xbintsc 0.5.32 → 0.5.36
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/README.md +10 -7
- package/README.zh-CN.md +10 -8
- package/dist/scripts/check-version.d.ts +27 -0
- package/dist/scripts/check-version.js +71 -0
- package/dist/scripts/check-version.js.map +1 -0
- package/dist/scripts/package-platform.d.ts +58 -0
- package/dist/scripts/package-platform.js +142 -0
- package/dist/scripts/package-platform.js.map +1 -0
- package/dist/scripts/verify-platform-package.d.ts +34 -0
- package/dist/scripts/verify-platform-package.js +161 -0
- package/dist/scripts/verify-platform-package.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/main.js +3 -1
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/driver/runtime-lib.d.ts +29 -4
- package/dist/src/driver/runtime-lib.js +68 -10
- package/dist/src/driver/runtime-lib.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/index.js +6 -5
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/media/index.js +4 -3
- package/dist/src/extensions/media/index.js.map +1 -1
- package/dist/src/parser/context.d.ts +41 -0
- package/dist/src/parser/context.js +89 -4
- package/dist/src/parser/context.js.map +1 -1
- package/dist/src/parser/literals.js +6 -12
- package/dist/src/parser/literals.js.map +1 -1
- package/dist/tests/driver/platform-package.test.d.ts +1 -0
- package/dist/tests/driver/platform-package.test.js +49 -0
- package/dist/tests/driver/platform-package.test.js.map +1 -0
- package/dist/tests/driver/runtime-lib.test.js +51 -4
- package/dist/tests/driver/runtime-lib.test.js.map +1 -1
- package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
- package/dist/tests/e2e/gui-audio.test.js +287 -0
- package/dist/tests/e2e/gui-audio.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +7 -0
- package/dist/tests/e2e/gui-helpers.js +1 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.js +78 -0
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/gui-style.test.js +55 -0
- package/dist/tests/e2e/gui-style.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl.test.js +258 -0
- package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
- package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
- package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
- package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
- package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
- package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
- package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2.test.js +45 -1
- package/dist/tests/e2e/gui-webgl2.test.js.map +1 -1
- package/dist/tests/extensions/gui-index.test.js +1 -0
- package/dist/tests/extensions/gui-index.test.js.map +1 -1
- package/dist/tests/extensions/media.test.js +1 -0
- package/dist/tests/extensions/media.test.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +30 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/platform-tarball.test.d.ts +1 -0
- package/dist/tests/platform-tarball.test.js +111 -0
- package/dist/tests/platform-tarball.test.js.map +1 -0
- package/dist/tests/version.test.js +6 -0
- package/dist/tests/version.test.js.map +1 -1
- package/doc/ai/contributing.md +11 -0
- package/doc/ai/extensions.md +7 -4
- package/doc/ai/troubleshooting.md +7 -5
- package/doc/ai/zh-CN/contributing.md +9 -0
- package/doc/ai/zh-CN/extensions.md +4 -2
- package/doc/ai/zh-CN/troubleshooting.md +5 -3
- package/doc/gui/api.md +61 -13
- package/doc/gui/build.md +11 -4
- package/doc/gui/dom.md +6 -3
- package/doc/gui/media.md +4 -0
- package/doc/gui/webgl.md +105 -46
- package/doc/gui.md +7 -3
- package/doc/npm-package.md +55 -27
- package/doc/requirements.md +6 -2
- package/doc/zh-CN/gui/api.md +51 -10
- package/doc/zh-CN/gui/build.md +7 -2
- package/doc/zh-CN/gui/dom.md +3 -1
- package/doc/zh-CN/gui/media.md +3 -0
- package/doc/zh-CN/gui/webgl.md +22 -16
- package/doc/zh-CN/gui.md +3 -2
- package/doc/zh-CN/npm-package.md +44 -23
- package/doc/zh-CN/requirements.md +4 -1
- package/package.json +17 -1
- package/runtime/ext_gui/audio.cpp +365 -153
- package/runtime/ext_gui/audio_buffer.cpp +166 -0
- package/runtime/ext_gui/audio_buffer.h +37 -0
- package/runtime/ext_gui/audio_decode.cpp +219 -0
- package/runtime/ext_gui/audio_decode.h +39 -0
- package/runtime/ext_gui/audio_device.cpp +87 -0
- package/runtime/ext_gui/audio_effects.cpp +163 -0
- package/runtime/ext_gui/audio_effects.h +25 -0
- package/runtime/ext_gui/audio_internal.h +30 -0
- package/runtime/ext_gui/audio_mixer.cpp +494 -0
- package/runtime/ext_gui/audio_mixer.h +238 -0
- package/runtime/ext_gui/audio_param.cpp +104 -0
- package/runtime/ext_gui/audio_param_object.cpp +155 -0
- package/runtime/ext_gui/browser.cpp +11 -1
- package/runtime/ext_gui/css_selector.cpp +21 -3
- package/runtime/ext_gui/dom_api_internal.h +16 -0
- package/runtime/ext_gui/dom_api_webgl.cpp +7 -0
- package/runtime/ext_gui/dom_api_webgl2.cpp +151 -36
- package/runtime/ext_gui/dom_api_webgl2_draw.cpp +3 -3
- package/runtime/ext_gui/dom_api_webgl_buffer.cpp +6 -2
- package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
- package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
- package/runtime/ext_gui/dom_api_webgl_shader.cpp +4 -4
- package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
- package/runtime/ext_gui/dom_api_webgl_state.cpp +169 -23
- package/runtime/ext_gui/dom_api_webgl_texture.cpp +276 -236
- package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
- package/runtime/ext_gui/gui.cpp +44 -9
- package/runtime/ext_gui/gui_engine.h +9 -4
- package/runtime/ext_gui/image_bitmap.cpp +28 -0
- package/runtime/ext_gui/image_bitmap.h +26 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
- package/runtime/ext_gui/media.cpp +32 -0
- package/runtime/ext_gui/media.h +13 -0
- package/runtime/ext_gui/media_api.h +23 -0
- package/runtime/ext_gui/webgl.h +152 -18
- package/runtime/ext_gui/webgl_blit.cpp +95 -0
- package/runtime/ext_gui/webgl_buffer.cpp +178 -263
- package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
- package/runtime/ext_gui/webgl_clear.cpp +142 -0
- package/runtime/ext_gui/webgl_compressed.cpp +180 -0
- package/runtime/ext_gui/webgl_context.cpp +241 -156
- package/runtime/ext_gui/webgl_draw.cpp +68 -3
- package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
- package/runtime/ext_gui/webgl_glsl.h +40 -0
- package/runtime/ext_gui/webgl_glsl_compile.cpp +6 -0
- package/runtime/ext_gui/webgl_glsl_rewrite.cpp +18 -0
- package/runtime/ext_gui/webgl_glsl_scan.cpp +20 -11
- package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
- package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
- package/runtime/ext_gui/webgl_pipeline.cpp +71 -19
- package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
- package/runtime/ext_gui/webgl_program_internal.h +6 -0
- package/runtime/ext_gui/webgl_shader.cpp +13 -1
- package/runtime/ext_gui/webgl_state.cpp +228 -0
- package/runtime/ext_gui/webgl_texture.cpp +144 -17
- package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
- package/runtime/ext_gui/webgl_uniform_block.h +31 -0
- package/runtime/ext_gui/webgl_webgl2.cpp +58 -5
- package/runtime/ext_media/decode_audio.cpp +308 -0
- package/runtime/ext_media/media_bridge.c +2 -0
- package/runtime/rt.h +17 -0
- package/runtime/xt_array_buffer.c +30 -0
- package/runtime/xt_typed_array/elements.inc +13 -0
- package/scripts/build-media.ts +14 -6
- package/scripts/build-runtime.ts +21 -16
- package/scripts/check-version.ts +39 -9
- package/scripts/package-platform.ts +183 -0
- package/scripts/set-platform-version.ts +62 -0
- package/scripts/verify-platform-package.ts +182 -0
- package/src/cli/main.ts +5 -1
- package/src/driver/runtime-lib.ts +73 -9
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/index.ts +6 -5
- package/src/extensions/media/index.ts +4 -3
- package/src/parser/context.ts +99 -4
- package/src/parser/literals.ts +6 -12
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* xbintsc GUI engine — the Web Audio surface a page expects.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* this interface later without touching the page.
|
|
4
|
+
* This unit is the JavaScript-facing half of Web Audio: AudioContext,
|
|
5
|
+
* AudioNode, AudioParam and AudioBuffer objects, all backed by the native graph
|
|
6
|
+
* in `audio_mixer.cpp`. `new AudioContext()` creates a mixer; the first
|
|
7
|
+
* `start()`/`resume()` opens the SDL audio device (best effort — a headless
|
|
8
|
+
* host stays silent), and `decodeAudioData` decodes WAV in-engine or asks the
|
|
9
|
+
* optional `media` extension for compressed formats (`audio_decode.cpp`).
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The offline diagnostic used by the tests is `new AudioContext({ __offline:
|
|
12
|
+
* true })` plus `ctx.__render(frames)`: it renders the same graph synchronously
|
|
13
|
+
* and returns interleaved stereo Float32 data without touching a device.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
#include "gui_engine.h"
|
|
17
|
-
#include "dom_api_internal.h"
|
|
18
17
|
|
|
18
|
+
#include <algorithm>
|
|
19
|
+
#include <cmath>
|
|
20
|
+
#include <cstring>
|
|
21
|
+
#include <memory>
|
|
22
|
+
#include <mutex>
|
|
19
23
|
#include <string>
|
|
24
|
+
#include <unordered_map>
|
|
25
|
+
#include <vector>
|
|
26
|
+
|
|
27
|
+
#include "audio_buffer.h"
|
|
28
|
+
#include "audio_decode.h"
|
|
29
|
+
#include "audio_internal.h"
|
|
30
|
+
#include "audio_mixer.h"
|
|
20
31
|
|
|
21
32
|
namespace {
|
|
22
33
|
|
|
34
|
+
using namespace xtgui;
|
|
35
|
+
|
|
23
36
|
xt_value fn_of(void *fn) { return xt_closure_new(fn, 0, NULL); }
|
|
24
37
|
|
|
25
38
|
void set_prop(xt_value target, const char *name, xt_value value) {
|
|
@@ -32,229 +45,371 @@ void set_number(xt_value target, const char *name, double value) {
|
|
|
32
45
|
set_prop(target, name, xt_number(value));
|
|
33
46
|
}
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
void set_string(xt_value target, const char *name, const char *value) {
|
|
49
|
+
set_prop(target, name, xt_string_from_cstr(value));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
double number_prop(xt_value object, const char *name) {
|
|
53
|
+
xt_value value = xt_object_get_cstr(object, name);
|
|
54
|
+
return XT_IS_NUMBER(value) ? xt_to_number(value) : 0.0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
std::mutex g_registry_mutex;
|
|
58
|
+
std::unordered_map<uint64_t, std::shared_ptr<AudioMixer>> g_mixers;
|
|
59
|
+
uint64_t g_next_context = 1;
|
|
60
|
+
|
|
61
|
+
std::shared_ptr<AudioMixer> mixer_of(xt_value object) {
|
|
62
|
+
return audio_mixer_by_id(audio_context_id(object));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
struct NodeRef {
|
|
66
|
+
std::shared_ptr<AudioMixer> mixer;
|
|
67
|
+
uint64_t node = 0;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
bool node_ref(xt_value object, NodeRef *out) {
|
|
71
|
+
out->mixer = mixer_of(object);
|
|
72
|
+
out->node = static_cast<uint64_t>(number_prop(object, "__xt_audio_node"));
|
|
73
|
+
return out->mixer != nullptr && out->node != 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
xt_value make_error(const char *message) {
|
|
77
|
+
xt_value text = xt_string_from_cstr(message);
|
|
78
|
+
xt_value error = xt_error_ctor_by_name(xt_string_from_cstr("Error"), 1, &text);
|
|
79
|
+
xt_object_set(error, xt_string_from_cstr("name"), xt_string_from_cstr("EncodingError"));
|
|
80
|
+
return error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Reject a `decodeAudioData` promise and invoke its optional error callback. */
|
|
84
|
+
xt_value decode_failure(xt_value callback, const std::string &message) {
|
|
85
|
+
xt_value error = make_error(message.c_str());
|
|
86
|
+
if (XT_IS_FUNCTION(callback)) {
|
|
87
|
+
xt_value argument = error;
|
|
88
|
+
xt_closure_call(callback, 1, &argument);
|
|
89
|
+
}
|
|
90
|
+
return xt_promise_reject(error);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Wrap interleaved float samples in a Float32Array (the `__render` hook). */
|
|
94
|
+
xt_value interleaved_array(const float *samples, uint32_t count) {
|
|
95
|
+
xt_value array_buffer = xt_array_buffer_alloc(count * static_cast<uint32_t>(sizeof(float)));
|
|
96
|
+
if (count > 0) {
|
|
97
|
+
xt_array_buffer_copy_bytes_in(array_buffer, 0,
|
|
98
|
+
reinterpret_cast<const uint8_t *>(samples),
|
|
99
|
+
count * static_cast<uint32_t>(sizeof(float)));
|
|
100
|
+
}
|
|
101
|
+
xt_value argument = array_buffer;
|
|
102
|
+
return xt_typed_array_ctor_by_name(xt_string_from_cstr("Float32Array"), 1, &argument);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** decodeAudioData resamples to the context rate; the mixer also handles a
|
|
106
|
+
* buffer at another rate, but matching the spec keeps `buffer.sampleRate`
|
|
107
|
+
* equal to `context.sampleRate`. Linear interpolation is plenty for the
|
|
108
|
+
* playback path that follows. */
|
|
109
|
+
void resample_decoded(DecodedAudio *audio, int target_rate) {
|
|
110
|
+
if (audio->sample_rate <= 0 || audio->channels <= 0 || audio->frames == 0 ||
|
|
111
|
+
audio->sample_rate == target_rate) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
double ratio = static_cast<double>(target_rate) / audio->sample_rate;
|
|
115
|
+
uint32_t frames = static_cast<uint32_t>(std::ceil(audio->frames * ratio));
|
|
116
|
+
std::vector<float> resampled(static_cast<size_t>(frames) * audio->channels, 0.0f);
|
|
117
|
+
for (uint32_t frame = 0; frame < frames; frame++) {
|
|
118
|
+
double position = frame / ratio;
|
|
119
|
+
uint32_t lower = static_cast<uint32_t>(position);
|
|
120
|
+
if (lower >= audio->frames) lower = audio->frames - 1;
|
|
121
|
+
uint32_t upper = lower + 1 < audio->frames ? lower + 1 : lower;
|
|
122
|
+
double fraction = position - lower;
|
|
123
|
+
for (int channel = 0; channel < audio->channels; channel++) {
|
|
124
|
+
double from = audio->interleaved[static_cast<size_t>(lower) * audio->channels + channel];
|
|
125
|
+
double to = audio->interleaved[static_cast<size_t>(upper) * audio->channels + channel];
|
|
126
|
+
resampled[static_cast<size_t>(frame) * audio->channels + channel] =
|
|
127
|
+
static_cast<float>(from + (to - from) * fraction);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
audio->interleaved.swap(resampled);
|
|
131
|
+
audio->frames = frames;
|
|
132
|
+
audio->sample_rate = target_rate;
|
|
41
133
|
}
|
|
42
134
|
|
|
43
|
-
|
|
44
|
-
|
|
135
|
+
/* -- AudioNode ------------------------------------------------------------ */
|
|
136
|
+
|
|
45
137
|
xt_value node_connect(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
46
|
-
(void)self;
|
|
47
138
|
(void)env;
|
|
48
139
|
xt_value target = xt_arg(argc, argv, 0);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
140
|
+
NodeRef source;
|
|
141
|
+
NodeRef destination;
|
|
142
|
+
if (node_ref(self, &source) && node_ref(target, &destination) &&
|
|
143
|
+
audio_context_id(self) == audio_context_id(target)) {
|
|
144
|
+
source.mixer->connect(source.node, destination.node);
|
|
55
145
|
}
|
|
56
146
|
return target;
|
|
57
147
|
}
|
|
58
148
|
|
|
149
|
+
xt_value node_disconnect(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
150
|
+
(void)env;
|
|
151
|
+
NodeRef source;
|
|
152
|
+
if (!node_ref(self, &source)) return XT_UNDEFINED;
|
|
153
|
+
if (argc > 0 && XT_IS_OBJECT(argv[0])) {
|
|
154
|
+
NodeRef target;
|
|
155
|
+
if (node_ref(argv[0], &target)) source.mixer->disconnect(source.node, target.node);
|
|
156
|
+
} else {
|
|
157
|
+
source.mixer->disconnect_all(source.node);
|
|
158
|
+
}
|
|
159
|
+
return XT_UNDEFINED;
|
|
160
|
+
}
|
|
161
|
+
|
|
59
162
|
xt_value node_start(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
60
163
|
(void)env;
|
|
61
|
-
|
|
62
|
-
(
|
|
164
|
+
NodeRef ref;
|
|
165
|
+
if (!node_ref(self, &ref)) return XT_UNDEFINED;
|
|
166
|
+
int kind = static_cast<int>(number_prop(self, "__xt_audio_kind"));
|
|
167
|
+
double when = argc > 0 && XT_IS_NUMBER(argv[0]) ? std::max(0.0, xt_to_number(argv[0]))
|
|
168
|
+
: ref.mixer->current_time();
|
|
169
|
+
if (kind == kAudioBufferSource) {
|
|
170
|
+
std::shared_ptr<AudioClip> clip = audio_clip_from_buffer(xt_object_get_cstr(self, "buffer"));
|
|
171
|
+
if (!clip) {
|
|
172
|
+
xt_value message = xt_string_from_cstr("AudioBufferSourceNode.start: buffer is not set");
|
|
173
|
+
xt_throw(xt_error_ctor_by_name(xt_string_from_cstr("Error"), 1, &message));
|
|
174
|
+
return XT_UNDEFINED;
|
|
175
|
+
}
|
|
176
|
+
double offset = argc > 1 ? xt_to_number(argv[1]) : 0.0;
|
|
177
|
+
double duration = argc > 2 ? xt_to_number(argv[2]) : -1.0;
|
|
178
|
+
bool loop = xt_truthy(xt_object_get_cstr(self, "loop"));
|
|
179
|
+
double loop_start = number_prop(self, "loopStart");
|
|
180
|
+
double loop_end = number_prop(self, "loopEnd");
|
|
181
|
+
ref.mixer->set_buffer(ref.node, clip, loop, loop_start, loop_end);
|
|
182
|
+
ref.mixer->start_source(ref.node, when, offset, duration);
|
|
183
|
+
} else {
|
|
184
|
+
ref.mixer->start_source(ref.node, when, 0.0, -1.0);
|
|
185
|
+
}
|
|
186
|
+
ref.mixer->start_device();
|
|
63
187
|
set_prop(self, "started", XT_TRUE);
|
|
64
188
|
return XT_UNDEFINED;
|
|
65
189
|
}
|
|
66
190
|
|
|
67
191
|
xt_value node_stop(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
68
192
|
(void)env;
|
|
69
|
-
|
|
70
|
-
(
|
|
193
|
+
NodeRef ref;
|
|
194
|
+
if (!node_ref(self, &ref)) return XT_UNDEFINED;
|
|
195
|
+
double when = argc > 0 && XT_IS_NUMBER(argv[0]) ? std::max(0.0, xt_to_number(argv[0]))
|
|
196
|
+
: ref.mixer->current_time();
|
|
197
|
+
ref.mixer->stop_source(ref.node, when);
|
|
71
198
|
set_prop(self, "started", XT_FALSE);
|
|
72
199
|
return XT_UNDEFINED;
|
|
73
200
|
}
|
|
74
201
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
xt_value param_value_get(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
202
|
+
xt_value node_type_get(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
78
203
|
(void)env;
|
|
79
204
|
(void)argc;
|
|
80
205
|
(void)argv;
|
|
81
|
-
xt_value value = xt_object_get_cstr(self, "
|
|
82
|
-
return
|
|
206
|
+
xt_value value = xt_object_get_cstr(self, "__xt_audio_type");
|
|
207
|
+
return XT_IS_STRING(value) ? value : xt_string_from_cstr("");
|
|
83
208
|
}
|
|
84
209
|
|
|
85
|
-
xt_value
|
|
210
|
+
xt_value node_type_set(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
86
211
|
(void)env;
|
|
87
|
-
(
|
|
88
|
-
|
|
89
|
-
|
|
212
|
+
xt_value value = xt_to_string(xt_arg(argc, argv, 0));
|
|
213
|
+
const char *name = XT_IS_STRING(value) ? xt_string_data(value) : "";
|
|
214
|
+
int kind = static_cast<int>(number_prop(self, "__xt_audio_kind"));
|
|
215
|
+
NodeRef ref;
|
|
216
|
+
node_ref(self, &ref);
|
|
217
|
+
if (kind == kAudioOscillator) {
|
|
218
|
+
AudioOscType type = kAudioOscSine;
|
|
219
|
+
if (std::strcmp(name, "square") == 0) type = kAudioOscSquare;
|
|
220
|
+
else if (std::strcmp(name, "sawtooth") == 0) type = kAudioOscSawtooth;
|
|
221
|
+
else if (std::strcmp(name, "triangle") == 0) type = kAudioOscTriangle;
|
|
222
|
+
else if (std::strcmp(name, "sine") != 0) return value;
|
|
223
|
+
if (ref.mixer) ref.mixer->set_osc_type(ref.node, type);
|
|
224
|
+
set_prop(self, "__xt_audio_type", value);
|
|
225
|
+
} else if (kind == kAudioBiquad) {
|
|
226
|
+
AudioBiquadType type = kAudioBiquadLowpass;
|
|
227
|
+
if (std::strcmp(name, "highpass") == 0) type = kAudioBiquadHighpass;
|
|
228
|
+
else if (std::strcmp(name, "bandpass") == 0) type = kAudioBiquadBandpass;
|
|
229
|
+
else if (std::strcmp(name, "lowshelf") == 0) type = kAudioBiquadLowshelf;
|
|
230
|
+
else if (std::strcmp(name, "highshelf") == 0) type = kAudioBiquadHighshelf;
|
|
231
|
+
else if (std::strcmp(name, "peaking") == 0) type = kAudioBiquadPeaking;
|
|
232
|
+
else if (std::strcmp(name, "notch") == 0) type = kAudioBiquadNotch;
|
|
233
|
+
else if (std::strcmp(name, "allpass") == 0) type = kAudioBiquadAllpass;
|
|
234
|
+
else if (std::strcmp(name, "lowpass") != 0) return value;
|
|
235
|
+
if (ref.mixer) ref.mixer->set_biquad_type(ref.node, type);
|
|
236
|
+
set_prop(self, "__xt_audio_type", value);
|
|
237
|
+
}
|
|
90
238
|
return value;
|
|
91
239
|
}
|
|
92
240
|
|
|
93
|
-
xt_value
|
|
94
|
-
|
|
95
|
-
(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
/** A ramp/curve call only records its target: nothing schedules yet. */
|
|
100
|
-
xt_value param_ramp(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
101
|
-
(void)env;
|
|
102
|
-
if (argc < 1) return self;
|
|
103
|
-
xt_value value = xt_arg(argc, argv, 0);
|
|
104
|
-
if (XT_IS_NUMBER(value)) set_prop(self, "__xt_audio_value", value);
|
|
105
|
-
return self;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
xt_value audio_param_build(double initial) {
|
|
109
|
-
xt_value param = xt_object_new();
|
|
110
|
-
set_number(param, "__xt_audio_value", initial);
|
|
111
|
-
xt_object_define_getter(param, xt_string_from_cstr("value"), fn_of((void *)param_value_get));
|
|
112
|
-
xt_object_define_setter(param, xt_string_from_cstr("value"), fn_of((void *)param_value_set));
|
|
113
|
-
set_method(param, "setValueAtTime", (void *)param_set_value_at_time);
|
|
114
|
-
set_method(param, "linearRampToValueAtTime", (void *)param_ramp);
|
|
115
|
-
set_method(param, "exponentialRampToValueAtTime", (void *)param_ramp);
|
|
116
|
-
set_method(param, "setTargetAtTime", (void *)param_ramp);
|
|
117
|
-
set_method(param, "cancelScheduledValues", (void *)noop);
|
|
118
|
-
set_method(param, "cancelAndHoldAtTime", (void *)noop);
|
|
119
|
-
return param;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** A node with the standard `AudioNode` shape. `params` names the AudioParams
|
|
123
|
-
* the node owns (`gain`, `frequency`, `Q`, …). */
|
|
124
|
-
xt_value audio_node_build(const char *type, const char *const *params, int param_count) {
|
|
241
|
+
xt_value build_node(uint64_t context, AudioNodeKind kind, const char *type,
|
|
242
|
+
const char *const *params, int param_count) {
|
|
243
|
+
std::shared_ptr<AudioMixer> mixer = audio_mixer_by_id(context);
|
|
244
|
+
if (!mixer) return XT_UNDEFINED;
|
|
245
|
+
uint64_t id = mixer->create_node(kind);
|
|
125
246
|
xt_value node = xt_object_new();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
247
|
+
set_number(node, "__xt_audio_ctx", static_cast<double>(context));
|
|
248
|
+
set_number(node, "__xt_audio_node", static_cast<double>(id));
|
|
249
|
+
set_number(node, "__xt_audio_kind", kind);
|
|
250
|
+
if (type != nullptr) set_string(node, "__xt_audio_type", type);
|
|
130
251
|
set_method(node, "connect", (void *)node_connect);
|
|
131
|
-
set_method(node, "disconnect", (void *)
|
|
252
|
+
set_method(node, "disconnect", (void *)node_disconnect);
|
|
132
253
|
set_method(node, "start", (void *)node_start);
|
|
133
254
|
set_method(node, "stop", (void *)node_stop);
|
|
134
255
|
set_number(node, "channelCount", 2);
|
|
256
|
+
for (int index = 0; index < param_count; index++) {
|
|
257
|
+
xt_value param = audio_param_build(context, id, index);
|
|
258
|
+
set_number(param, "defaultValue", mixer->param_value(id, index));
|
|
259
|
+
set_prop(node, params[index], param);
|
|
260
|
+
}
|
|
261
|
+
if (kind == kAudioOscillator || kind == kAudioBiquad) {
|
|
262
|
+
xt_object_define_getter(node, xt_string_from_cstr("type"), fn_of((void *)node_type_get));
|
|
263
|
+
xt_object_define_setter(node, xt_string_from_cstr("type"), fn_of((void *)node_type_set));
|
|
264
|
+
}
|
|
265
|
+
if (kind == kAudioBufferSource) {
|
|
266
|
+
set_prop(node, "buffer", XT_NULL);
|
|
267
|
+
set_prop(node, "loop", XT_FALSE);
|
|
268
|
+
set_number(node, "loopStart", 0);
|
|
269
|
+
set_number(node, "loopEnd", 0);
|
|
270
|
+
}
|
|
271
|
+
if (kind == kAudioOscillator || kind == kAudioBufferSource ||
|
|
272
|
+
kind == kAudioConstantSource) {
|
|
273
|
+
set_prop(node, "started", XT_FALSE);
|
|
274
|
+
}
|
|
135
275
|
return node;
|
|
136
276
|
}
|
|
137
277
|
|
|
138
278
|
/* -- AudioContext --------------------------------------------------------- */
|
|
139
279
|
|
|
140
|
-
xt_value buffer_get_channel_data(xt_value self, xt_value env, int32_t argc, xt_value *argv);
|
|
141
|
-
|
|
142
280
|
xt_value ctx_create_gain(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
143
|
-
(void)self;
|
|
144
281
|
(void)env;
|
|
145
282
|
(void)argc;
|
|
146
283
|
(void)argv;
|
|
147
284
|
static const char *const params[] = {"gain"};
|
|
148
|
-
return
|
|
285
|
+
return build_node(audio_context_id(self), kAudioGain, nullptr, params, 1);
|
|
149
286
|
}
|
|
150
287
|
|
|
151
288
|
xt_value ctx_create_oscillator(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
152
|
-
(void)self;
|
|
153
289
|
(void)env;
|
|
154
290
|
(void)argc;
|
|
155
291
|
(void)argv;
|
|
156
292
|
static const char *const params[] = {"frequency", "detune"};
|
|
157
|
-
|
|
158
|
-
set_prop(node, "type", xt_string_from_cstr("sine"));
|
|
159
|
-
return node;
|
|
293
|
+
return build_node(audio_context_id(self), kAudioOscillator, "sine", params, 2);
|
|
160
294
|
}
|
|
161
295
|
|
|
162
296
|
xt_value ctx_create_buffer_source(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
163
|
-
(void)self;
|
|
164
297
|
(void)env;
|
|
165
298
|
(void)argc;
|
|
166
299
|
(void)argv;
|
|
167
300
|
static const char *const params[] = {"playbackRate", "detune"};
|
|
168
|
-
|
|
169
|
-
set_prop(node, "buffer", XT_NULL);
|
|
170
|
-
set_prop(node, "loop", XT_FALSE);
|
|
171
|
-
set_number(node, "loopStart", 0);
|
|
172
|
-
set_number(node, "loopEnd", 0);
|
|
173
|
-
return node;
|
|
301
|
+
return build_node(audio_context_id(self), kAudioBufferSource, nullptr, params, 2);
|
|
174
302
|
}
|
|
175
303
|
|
|
176
304
|
xt_value ctx_create_biquad(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
177
|
-
(void)self;
|
|
178
305
|
(void)env;
|
|
179
306
|
(void)argc;
|
|
180
307
|
(void)argv;
|
|
181
308
|
static const char *const params[] = {"frequency", "detune", "Q", "gain"};
|
|
182
|
-
|
|
183
|
-
set_prop(node, "type", xt_string_from_cstr("lowpass"));
|
|
184
|
-
return node;
|
|
309
|
+
return build_node(audio_context_id(self), kAudioBiquad, "lowpass", params, 4);
|
|
185
310
|
}
|
|
186
311
|
|
|
187
312
|
xt_value ctx_create_compressor(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
188
|
-
(void)self;
|
|
189
313
|
(void)env;
|
|
190
314
|
(void)argc;
|
|
191
315
|
(void)argv;
|
|
192
|
-
static const char *const params[] = {"threshold", "knee",
|
|
193
|
-
|
|
194
|
-
|
|
316
|
+
static const char *const params[] = {"threshold", "knee", "ratio", "attack", "release"};
|
|
317
|
+
return build_node(audio_context_id(self), kAudioCompressor, nullptr, params, 5);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
xt_value ctx_create_stereo_panner(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
321
|
+
(void)env;
|
|
322
|
+
(void)argc;
|
|
323
|
+
(void)argv;
|
|
324
|
+
static const char *const params[] = {"pan"};
|
|
325
|
+
return build_node(audio_context_id(self), kAudioStereoPanner, nullptr, params, 1);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
xt_value ctx_create_constant_source(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
329
|
+
(void)env;
|
|
330
|
+
(void)argc;
|
|
331
|
+
(void)argv;
|
|
332
|
+
static const char *const params[] = {"offset"};
|
|
333
|
+
return build_node(audio_context_id(self), kAudioConstantSource, nullptr, params, 1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
xt_value ctx_create_delay(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
337
|
+
(void)env;
|
|
338
|
+
static const char *const params[] = {"delayTime"};
|
|
339
|
+
uint64_t context = audio_context_id(self);
|
|
340
|
+
xt_value node = build_node(context, kAudioDelay, nullptr, params, 1);
|
|
341
|
+
std::shared_ptr<AudioMixer> mixer = audio_mixer_by_id(context);
|
|
342
|
+
if (mixer && XT_IS_OBJECT(node)) {
|
|
343
|
+
double maximum = argc > 0 && XT_IS_NUMBER(argv[0]) ? xt_to_number(argv[0]) : 1.0;
|
|
344
|
+
mixer->set_delay_max(static_cast<uint64_t>(number_prop(node, "__xt_audio_node")), maximum);
|
|
345
|
+
}
|
|
346
|
+
return node;
|
|
195
347
|
}
|
|
196
348
|
|
|
197
349
|
/** `createBuffer(channels, length, sampleRate)`: real `Float32Array` channels. */
|
|
198
350
|
xt_value ctx_create_buffer(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
199
351
|
(void)self;
|
|
200
352
|
(void)env;
|
|
201
|
-
int channels = (
|
|
202
|
-
int length = (
|
|
353
|
+
int channels = static_cast<int>(xt_to_number(xt_arg(argc, argv, 0)));
|
|
354
|
+
int length = static_cast<int>(xt_to_number(xt_arg(argc, argv, 1)));
|
|
203
355
|
double rate = xt_to_number(xt_arg(argc, argv, 2));
|
|
204
356
|
if (channels < 1) channels = 1;
|
|
357
|
+
if (channels > 32) channels = 32;
|
|
205
358
|
if (length < 0) length = 0;
|
|
206
|
-
|
|
207
|
-
for (
|
|
208
|
-
|
|
209
|
-
xt_value channel = xt_typed_array_ctor_by_name(xt_string_from_cstr("Float32Array"), 1, &size);
|
|
210
|
-
xt_array_push(array, channel);
|
|
211
|
-
}
|
|
212
|
-
xt_value buffer = xt_object_new();
|
|
213
|
-
set_prop(buffer, "__xt_audio_channels", array);
|
|
214
|
-
set_method(buffer, "getChannelData", (void *)buffer_get_channel_data);
|
|
215
|
-
set_number(buffer, "numberOfChannels", channels);
|
|
216
|
-
set_number(buffer, "length", length);
|
|
217
|
-
set_number(buffer, "sampleRate", rate);
|
|
218
|
-
if (rate > 0) set_number(buffer, "duration", (double)length / rate);
|
|
219
|
-
return buffer;
|
|
359
|
+
std::vector<std::vector<float>> data(static_cast<size_t>(channels));
|
|
360
|
+
for (std::vector<float> &channel : data) channel.assign(static_cast<size_t>(length), 0.0f);
|
|
361
|
+
return audio_buffer_create(data, rate > 0 ? static_cast<int>(rate) : kAudioMixRate);
|
|
220
362
|
}
|
|
221
363
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
xt_value empty_channel(void) {
|
|
225
|
-
xt_value zero = xt_number(0);
|
|
226
|
-
return xt_typed_array_ctor_by_name(xt_string_from_cstr("Float32Array"), 1, &zero);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
xt_value buffer_get_channel_data(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
364
|
+
xt_value ctx_decode_audio_data(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
365
|
+
(void)self;
|
|
230
366
|
(void)env;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
xt_value
|
|
234
|
-
|
|
235
|
-
if (!
|
|
236
|
-
|
|
237
|
-
|
|
367
|
+
xt_value input = xt_arg(argc, argv, 0);
|
|
368
|
+
xt_value success = xt_arg(argc, argv, 1);
|
|
369
|
+
xt_value failure = xt_arg(argc, argv, 2);
|
|
370
|
+
std::vector<uint8_t> bytes;
|
|
371
|
+
if (!audio_bytes_from_value(input, &bytes)) {
|
|
372
|
+
return decode_failure(failure, "Unable to decode audio data: expected an ArrayBuffer");
|
|
373
|
+
}
|
|
374
|
+
DecodedAudio decoded;
|
|
375
|
+
std::string error;
|
|
376
|
+
if (!decode_audio_bytes(bytes.data(), bytes.size(), &decoded, &error)) {
|
|
377
|
+
return decode_failure(failure, "Unable to decode audio data: " + error);
|
|
378
|
+
}
|
|
379
|
+
resample_decoded(&decoded, kAudioMixRate);
|
|
380
|
+
std::vector<std::vector<float>> channels(static_cast<size_t>(decoded.channels));
|
|
381
|
+
for (int channel = 0; channel < decoded.channels; channel++) {
|
|
382
|
+
std::vector<float> &samples = channels[static_cast<size_t>(channel)];
|
|
383
|
+
samples.resize(decoded.frames);
|
|
384
|
+
for (uint32_t frame = 0; frame < decoded.frames; frame++) {
|
|
385
|
+
samples[frame] = decoded.interleaved[static_cast<size_t>(frame) * decoded.channels + channel];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
xt_value buffer = audio_buffer_create(channels, decoded.sample_rate);
|
|
389
|
+
if (XT_IS_FUNCTION(success)) {
|
|
390
|
+
xt_value argument = buffer;
|
|
391
|
+
xt_closure_call(success, 1, &argument);
|
|
392
|
+
}
|
|
393
|
+
return xt_promise_resolve(buffer);
|
|
238
394
|
}
|
|
239
395
|
|
|
240
|
-
xt_value
|
|
241
|
-
(void)self;
|
|
396
|
+
xt_value ctx_current_time_get(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
242
397
|
(void)env;
|
|
243
|
-
/* No decoder: resolve with an empty buffer so a `await decodeAudioData(…)`
|
|
244
|
-
* finishes instead of hanging the page. */
|
|
245
|
-
xt_value size = xt_number(0);
|
|
246
|
-
xt_value buffer = ctx_create_buffer(XT_UNDEFINED, XT_UNDEFINED, 3,
|
|
247
|
-
(xt_value[]){xt_number(1), size, xt_number(44100)});
|
|
248
398
|
(void)argc;
|
|
249
399
|
(void)argv;
|
|
250
|
-
|
|
400
|
+
std::shared_ptr<AudioMixer> mixer = mixer_of(self);
|
|
401
|
+
if (mixer) return xt_number(mixer->current_time());
|
|
402
|
+
xt_value stored = xt_object_get_cstr(self, "__xt_audio_time");
|
|
403
|
+
return XT_IS_NUMBER(stored) ? stored : xt_number(0);
|
|
251
404
|
}
|
|
252
405
|
|
|
253
406
|
xt_value ctx_resume(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
254
407
|
(void)env;
|
|
255
408
|
(void)argc;
|
|
256
409
|
(void)argv;
|
|
257
|
-
|
|
410
|
+
std::shared_ptr<AudioMixer> mixer = mixer_of(self);
|
|
411
|
+
if (mixer) mixer->resume_device();
|
|
412
|
+
set_string(self, "state", "running");
|
|
258
413
|
return xt_promise_resolve(XT_UNDEFINED);
|
|
259
414
|
}
|
|
260
415
|
|
|
@@ -262,7 +417,9 @@ xt_value ctx_suspend(xt_value self, xt_value env, int32_t argc, xt_value *argv)
|
|
|
262
417
|
(void)env;
|
|
263
418
|
(void)argc;
|
|
264
419
|
(void)argv;
|
|
265
|
-
|
|
420
|
+
std::shared_ptr<AudioMixer> mixer = mixer_of(self);
|
|
421
|
+
if (mixer) mixer->suspend_device();
|
|
422
|
+
set_string(self, "state", "suspended");
|
|
266
423
|
return xt_promise_resolve(XT_UNDEFINED);
|
|
267
424
|
}
|
|
268
425
|
|
|
@@ -270,13 +427,37 @@ xt_value ctx_close(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
|
270
427
|
(void)env;
|
|
271
428
|
(void)argc;
|
|
272
429
|
(void)argv;
|
|
273
|
-
|
|
430
|
+
uint64_t context = audio_context_id(self);
|
|
431
|
+
std::shared_ptr<AudioMixer> mixer = audio_mixer_by_id(context);
|
|
432
|
+
if (mixer) {
|
|
433
|
+
set_number(self, "__xt_audio_time", mixer->current_time());
|
|
434
|
+
mixer->close_device();
|
|
435
|
+
mixer->shutdown();
|
|
436
|
+
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
|
437
|
+
g_mixers.erase(context);
|
|
438
|
+
}
|
|
439
|
+
set_string(self, "state", "closed");
|
|
274
440
|
return xt_promise_resolve(XT_UNDEFINED);
|
|
275
441
|
}
|
|
276
442
|
|
|
443
|
+
/** Offline diagnostic: render `frames` stereo frames from an offline context
|
|
444
|
+
* and return them as an interleaved Float32Array. */
|
|
445
|
+
xt_value ctx_render(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
|
|
446
|
+
(void)env;
|
|
447
|
+
std::shared_ptr<AudioMixer> mixer = mixer_of(self);
|
|
448
|
+
if (!mixer || !mixer->offline()) return XT_NULL;
|
|
449
|
+
double requested = xt_to_number(xt_arg(argc, argv, 0));
|
|
450
|
+
if (!(requested > 0)) return interleaved_array(nullptr, 0);
|
|
451
|
+
if (requested > 48000.0 * 600.0) requested = 48000.0 * 600.0;
|
|
452
|
+
uint32_t frames = static_cast<uint32_t>(requested);
|
|
453
|
+
std::vector<float> samples(static_cast<size_t>(frames) * kAudioMixChannels, 0.0f);
|
|
454
|
+
mixer->render_offline(samples.data(), static_cast<int>(frames));
|
|
455
|
+
return interleaved_array(samples.data(), frames * kAudioMixChannels);
|
|
456
|
+
}
|
|
457
|
+
|
|
277
458
|
/** The context prototype: also the `instanceof AudioContext` target, and the
|
|
278
459
|
* prototype of the object `new AudioContext()` builds. */
|
|
279
|
-
xt_value audio_proto(
|
|
460
|
+
xt_value audio_proto() {
|
|
280
461
|
static xt_value proto = XT_UNDEFINED;
|
|
281
462
|
if (XT_IS_OBJECT(proto)) return proto;
|
|
282
463
|
xt_value built = xt_object_new();
|
|
@@ -285,11 +466,15 @@ xt_value audio_proto(void) {
|
|
|
285
466
|
set_method(built, "createBufferSource", (void *)ctx_create_buffer_source);
|
|
286
467
|
set_method(built, "createBiquadFilter", (void *)ctx_create_biquad);
|
|
287
468
|
set_method(built, "createDynamicsCompressor", (void *)ctx_create_compressor);
|
|
469
|
+
set_method(built, "createStereoPanner", (void *)ctx_create_stereo_panner);
|
|
470
|
+
set_method(built, "createConstantSource", (void *)ctx_create_constant_source);
|
|
471
|
+
set_method(built, "createDelay", (void *)ctx_create_delay);
|
|
288
472
|
set_method(built, "createBuffer", (void *)ctx_create_buffer);
|
|
289
473
|
set_method(built, "decodeAudioData", (void *)ctx_decode_audio_data);
|
|
290
474
|
set_method(built, "resume", (void *)ctx_resume);
|
|
291
475
|
set_method(built, "suspend", (void *)ctx_suspend);
|
|
292
476
|
set_method(built, "close", (void *)ctx_close);
|
|
477
|
+
set_method(built, "__render", (void *)ctx_render);
|
|
293
478
|
proto = built;
|
|
294
479
|
xt_gc_add_root(&proto);
|
|
295
480
|
return proto;
|
|
@@ -297,20 +482,47 @@ xt_value audio_proto(void) {
|
|
|
297
482
|
|
|
298
483
|
} // namespace
|
|
299
484
|
|
|
485
|
+
namespace xtgui {
|
|
486
|
+
|
|
487
|
+
std::shared_ptr<AudioMixer> audio_mixer_by_id(uint64_t id) {
|
|
488
|
+
if (id == 0) return nullptr;
|
|
489
|
+
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
|
490
|
+
auto it = g_mixers.find(id);
|
|
491
|
+
return it == g_mixers.end() ? nullptr : it->second;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
uint64_t audio_context_id(xt_value object) {
|
|
495
|
+
xt_value value = xt_object_get_cstr(object, "__xt_audio_ctx");
|
|
496
|
+
return XT_IS_NUMBER(value) ? static_cast<uint64_t>(xt_to_number(value)) : 0;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
} // namespace xtgui
|
|
500
|
+
|
|
300
501
|
xt_value xt_gui_audio_proto(void) { return audio_proto(); }
|
|
301
502
|
|
|
302
503
|
xt_value xt_gui_audio_create(int32_t argc, xt_value *argv) {
|
|
303
|
-
|
|
304
|
-
(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
504
|
+
bool offline = false;
|
|
505
|
+
xt_value options = xt_arg(argc, argv, 0);
|
|
506
|
+
if (XT_IS_OBJECT(options)) {
|
|
507
|
+
offline = xt_truthy(xt_object_get_cstr(options, "__offline"));
|
|
508
|
+
}
|
|
509
|
+
uint64_t context = g_next_context++;
|
|
510
|
+
std::shared_ptr<AudioMixer> mixer = std::make_shared<AudioMixer>(offline);
|
|
511
|
+
{
|
|
512
|
+
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
|
513
|
+
g_mixers[context] = mixer;
|
|
514
|
+
}
|
|
515
|
+
xt_value value = xt_object_new_with_proto(audio_proto());
|
|
516
|
+
set_number(value, "__xt_audio_ctx", static_cast<double>(context));
|
|
517
|
+
set_string(value, "state", "running");
|
|
518
|
+
set_number(value, "sampleRate", kAudioMixRate);
|
|
519
|
+
set_number(value, "baseLatency", 0.01);
|
|
520
|
+
set_number(value, "outputLatency", 0.01);
|
|
521
|
+
xt_object_define_getter(value, xt_string_from_cstr("currentTime"),
|
|
522
|
+
fn_of((void *)ctx_current_time_get));
|
|
523
|
+
xt_value destination = build_node(context, kAudioDestination, nullptr, nullptr, 0);
|
|
524
|
+
set_prop(value, "destination", destination);
|
|
313
525
|
xt_value listener = xt_object_new();
|
|
314
|
-
set_prop(
|
|
315
|
-
return
|
|
526
|
+
set_prop(value, "listener", listener);
|
|
527
|
+
return value;
|
|
316
528
|
}
|