xbintsc 0.5.32 → 0.5.37

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.
Files changed (222) hide show
  1. package/README.md +10 -7
  2. package/README.zh-CN.md +10 -8
  3. package/dist/scripts/check-version.d.ts +27 -0
  4. package/dist/scripts/check-version.js +71 -0
  5. package/dist/scripts/check-version.js.map +1 -0
  6. package/dist/scripts/package-platform.d.ts +58 -0
  7. package/dist/scripts/package-platform.js +142 -0
  8. package/dist/scripts/package-platform.js.map +1 -0
  9. package/dist/scripts/verify-platform-package.d.ts +34 -0
  10. package/dist/scripts/verify-platform-package.js +161 -0
  11. package/dist/scripts/verify-platform-package.js.map +1 -0
  12. package/dist/src/cli/help.d.ts +1 -1
  13. package/dist/src/cli/main.js +3 -1
  14. package/dist/src/cli/main.js.map +1 -1
  15. package/dist/src/driver/runtime-lib.d.ts +29 -4
  16. package/dist/src/driver/runtime-lib.js +68 -10
  17. package/dist/src/driver/runtime-lib.js.map +1 -1
  18. package/dist/src/driver/version.d.ts +1 -1
  19. package/dist/src/driver/version.js +1 -1
  20. package/dist/src/extensions/gui/index.js +18 -5
  21. package/dist/src/extensions/gui/index.js.map +1 -1
  22. package/dist/src/extensions/media/index.js +4 -3
  23. package/dist/src/extensions/media/index.js.map +1 -1
  24. package/dist/src/parser/context.d.ts +41 -0
  25. package/dist/src/parser/context.js +89 -4
  26. package/dist/src/parser/context.js.map +1 -1
  27. package/dist/src/parser/literals.js +6 -12
  28. package/dist/src/parser/literals.js.map +1 -1
  29. package/dist/tests/driver/platform-package.test.d.ts +1 -0
  30. package/dist/tests/driver/platform-package.test.js +49 -0
  31. package/dist/tests/driver/platform-package.test.js.map +1 -0
  32. package/dist/tests/driver/runtime-lib.test.js +51 -4
  33. package/dist/tests/driver/runtime-lib.test.js.map +1 -1
  34. package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
  35. package/dist/tests/e2e/gui-audio.test.js +287 -0
  36. package/dist/tests/e2e/gui-audio.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-browser.test.js +65 -0
  38. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  39. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  40. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  41. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  42. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  43. package/dist/tests/e2e/gui-dom.test.js +215 -0
  44. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  45. package/dist/tests/e2e/gui-events.test.js +112 -0
  46. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  47. package/dist/tests/e2e/gui-helpers.d.ts +7 -0
  48. package/dist/tests/e2e/gui-helpers.js +1 -0
  49. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  50. package/dist/tests/e2e/gui-media.test.js +78 -0
  51. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  52. package/dist/tests/e2e/gui-style.test.js +55 -0
  53. package/dist/tests/e2e/gui-style.test.js.map +1 -1
  54. package/dist/tests/e2e/gui-webgl.test.js +258 -0
  55. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  56. package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
  57. package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
  58. package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
  59. package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
  60. package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
  61. package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
  62. package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
  63. package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
  64. package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
  66. package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
  67. package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
  69. package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
  70. package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
  71. package/dist/tests/e2e/gui-webgl2.test.js +45 -1
  72. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -1
  73. package/dist/tests/extensions/gui-index.test.js +1 -0
  74. package/dist/tests/extensions/gui-index.test.js.map +1 -1
  75. package/dist/tests/extensions/media.test.js +1 -0
  76. package/dist/tests/extensions/media.test.js.map +1 -1
  77. package/dist/tests/parser/expressions.test.js +30 -0
  78. package/dist/tests/parser/expressions.test.js.map +1 -1
  79. package/dist/tests/platform-tarball.test.d.ts +1 -0
  80. package/dist/tests/platform-tarball.test.js +111 -0
  81. package/dist/tests/platform-tarball.test.js.map +1 -0
  82. package/dist/tests/version.test.js +6 -0
  83. package/dist/tests/version.test.js.map +1 -1
  84. package/doc/ai/contributing.md +11 -0
  85. package/doc/ai/extensions.md +7 -4
  86. package/doc/ai/troubleshooting.md +7 -5
  87. package/doc/ai/zh-CN/contributing.md +9 -0
  88. package/doc/ai/zh-CN/extensions.md +4 -2
  89. package/doc/ai/zh-CN/troubleshooting.md +5 -3
  90. package/doc/gui/api.md +99 -22
  91. package/doc/gui/build.md +11 -4
  92. package/doc/gui/canvas.md +49 -18
  93. package/doc/gui/dom.md +57 -7
  94. package/doc/gui/media.md +4 -0
  95. package/doc/gui/webgl.md +105 -46
  96. package/doc/gui.md +7 -3
  97. package/doc/npm-package.md +55 -27
  98. package/doc/requirements.md +6 -2
  99. package/doc/zh-CN/gui/api.md +84 -14
  100. package/doc/zh-CN/gui/build.md +7 -2
  101. package/doc/zh-CN/gui/canvas.md +44 -19
  102. package/doc/zh-CN/gui/dom.md +48 -4
  103. package/doc/zh-CN/gui/media.md +3 -0
  104. package/doc/zh-CN/gui/webgl.md +22 -16
  105. package/doc/zh-CN/gui.md +3 -2
  106. package/doc/zh-CN/npm-package.md +44 -23
  107. package/doc/zh-CN/requirements.md +4 -1
  108. package/package.json +17 -1
  109. package/runtime/ext_gui/audio.cpp +365 -153
  110. package/runtime/ext_gui/audio_buffer.cpp +166 -0
  111. package/runtime/ext_gui/audio_buffer.h +37 -0
  112. package/runtime/ext_gui/audio_decode.cpp +219 -0
  113. package/runtime/ext_gui/audio_decode.h +39 -0
  114. package/runtime/ext_gui/audio_device.cpp +87 -0
  115. package/runtime/ext_gui/audio_effects.cpp +163 -0
  116. package/runtime/ext_gui/audio_effects.h +25 -0
  117. package/runtime/ext_gui/audio_internal.h +30 -0
  118. package/runtime/ext_gui/audio_mixer.cpp +494 -0
  119. package/runtime/ext_gui/audio_mixer.h +238 -0
  120. package/runtime/ext_gui/audio_param.cpp +104 -0
  121. package/runtime/ext_gui/audio_param_object.cpp +155 -0
  122. package/runtime/ext_gui/browser.cpp +45 -50
  123. package/runtime/ext_gui/browser_abort.cpp +249 -0
  124. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  125. package/runtime/ext_gui/browser_media.cpp +176 -0
  126. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  127. package/runtime/ext_gui/canvas.cpp +88 -30
  128. package/runtime/ext_gui/canvas.h +74 -9
  129. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  130. package/runtime/ext_gui/canvas_internal.h +8 -5
  131. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  132. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  133. package/runtime/ext_gui/canvas_style.cpp +4 -2
  134. package/runtime/ext_gui/css_selector.cpp +21 -3
  135. package/runtime/ext_gui/dom.h +4 -0
  136. package/runtime/ext_gui/dom_api.cpp +1 -0
  137. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  138. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  139. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  140. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  141. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  142. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  143. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  144. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  145. package/runtime/ext_gui/dom_api_internal.h +71 -2
  146. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  147. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  148. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  149. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  150. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  151. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  152. package/runtime/ext_gui/dom_api_webgl.cpp +7 -0
  153. package/runtime/ext_gui/dom_api_webgl2.cpp +151 -36
  154. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +3 -3
  155. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +6 -2
  156. package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
  157. package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
  158. package/runtime/ext_gui/dom_api_webgl_shader.cpp +4 -4
  159. package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
  160. package/runtime/ext_gui/dom_api_webgl_state.cpp +169 -23
  161. package/runtime/ext_gui/dom_api_webgl_texture.cpp +276 -236
  162. package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
  163. package/runtime/ext_gui/gui.cpp +103 -14
  164. package/runtime/ext_gui/gui_engine.h +56 -4
  165. package/runtime/ext_gui/gui_events.cpp +80 -9
  166. package/runtime/ext_gui/image_bitmap.cpp +28 -0
  167. package/runtime/ext_gui/image_bitmap.h +26 -0
  168. package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
  169. package/runtime/ext_gui/listener_options.h +43 -0
  170. package/runtime/ext_gui/media.cpp +32 -0
  171. package/runtime/ext_gui/media.h +13 -0
  172. package/runtime/ext_gui/media_api.h +23 -0
  173. package/runtime/ext_gui/paint.cpp +28 -0
  174. package/runtime/ext_gui/paint.h +4 -0
  175. package/runtime/ext_gui/webgl.h +152 -18
  176. package/runtime/ext_gui/webgl_blit.cpp +95 -0
  177. package/runtime/ext_gui/webgl_buffer.cpp +178 -263
  178. package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
  179. package/runtime/ext_gui/webgl_clear.cpp +142 -0
  180. package/runtime/ext_gui/webgl_compressed.cpp +180 -0
  181. package/runtime/ext_gui/webgl_context.cpp +241 -156
  182. package/runtime/ext_gui/webgl_draw.cpp +68 -3
  183. package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
  184. package/runtime/ext_gui/webgl_glsl.h +40 -0
  185. package/runtime/ext_gui/webgl_glsl_compile.cpp +6 -0
  186. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +18 -0
  187. package/runtime/ext_gui/webgl_glsl_scan.cpp +20 -11
  188. package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
  189. package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
  190. package/runtime/ext_gui/webgl_pipeline.cpp +71 -19
  191. package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
  192. package/runtime/ext_gui/webgl_program_internal.h +6 -0
  193. package/runtime/ext_gui/webgl_shader.cpp +13 -1
  194. package/runtime/ext_gui/webgl_state.cpp +228 -0
  195. package/runtime/ext_gui/webgl_texture.cpp +144 -17
  196. package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
  197. package/runtime/ext_gui/webgl_uniform_block.h +31 -0
  198. package/runtime/ext_gui/webgl_webgl2.cpp +58 -5
  199. package/runtime/ext_gui/window.cpp +14 -65
  200. package/runtime/ext_gui/window_events.cpp +125 -0
  201. package/runtime/ext_media/decode_audio.cpp +308 -0
  202. package/runtime/ext_media/media_bridge.c +2 -0
  203. package/runtime/rt.h +43 -0
  204. package/runtime/rt_internal.h +4 -0
  205. package/runtime/xt_array_buffer.c +30 -0
  206. package/runtime/xt_builtins.c +0 -21
  207. package/runtime/xt_containers/access.inc +94 -2
  208. package/runtime/xt_containers/objects.inc +29 -31
  209. package/runtime/xt_typed_array/elements.inc +13 -0
  210. package/scripts/build-media.ts +14 -6
  211. package/scripts/build-runtime.ts +21 -16
  212. package/scripts/check-version.ts +39 -9
  213. package/scripts/package-platform.ts +183 -0
  214. package/scripts/set-platform-version.ts +62 -0
  215. package/scripts/verify-platform-package.ts +182 -0
  216. package/src/cli/main.ts +5 -1
  217. package/src/driver/runtime-lib.ts +73 -9
  218. package/src/driver/version.ts +1 -1
  219. package/src/extensions/gui/index.ts +18 -5
  220. package/src/extensions/media/index.ts +4 -3
  221. package/src/parser/context.ts +99 -4
  222. 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
- * There is no audio backend yet: the graph is created, values are stored and
5
- * read back, but nothing is rendered to the sound device. That is enough for
6
- * the common pattern of building the graph once (`createGain`, `connect`,
7
- * `AudioParam.setValueAtTime`, `createBuffer` + `getChannelData`) and then
8
- * driving it with `start`/`stop` — the program keeps running instead of
9
- * failing at `new AudioContext()`, and a real mixer can be dropped in behind
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
- * `createBuffer` hands out real `Float32Array` channels, so a page that fills
13
- * a buffer procedurally (noise beds, hand-built waveforms) works unchanged.
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
- xt_value noop(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
36
- (void)self;
37
- (void)env;
38
- (void)argc;
39
- (void)argv;
40
- return XT_UNDEFINED;
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
- /** `connect(node)` returns the destination, so `a.connect(b).connect(c)` chains
44
- * the same way it does in a browser. */
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
- if (XT_IS_OBJECT(target)) {
50
- xt_value inputs = xt_object_get_cstr(target, "__xt_audio_inputs");
51
- if (!XT_IS_OBJECT(inputs)) {
52
- inputs = xt_object_new();
53
- xt_object_set(target, xt_string_from_cstr("__xt_audio_inputs"), inputs);
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
- (void)argc;
62
- (void)argv;
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
- (void)argc;
70
- (void)argv;
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
- /* -- AudioParam ----------------------------------------------------------- */
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, "__xt_audio_value");
82
- return XT_IS_NUMBER(value) ? value : xt_number(0);
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 param_value_set(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
210
+ xt_value node_type_set(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
86
211
  (void)env;
87
- (void)self;
88
- xt_value value = xt_arg(argc, argv, 0);
89
- if (XT_IS_NUMBER(value)) set_prop(self, "__xt_audio_value", value);
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 param_set_value_at_time(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
94
- (void)env;
95
- (void)argc;
96
- return param_value_set(self, env, 1, argv);
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
- if (type != NULL) set_prop(node, "type", xt_string_from_cstr(type));
127
- for (int index = 0; index < param_count; index++) {
128
- set_prop(node, params[index], audio_param_build(0.0));
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 *)noop);
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 audio_node_build("gain", params, 1);
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
- xt_value node = audio_node_build(NULL, params, 2);
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
- xt_value node = audio_node_build(NULL, params, 2);
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
- xt_value node = audio_node_build(NULL, params, 4);
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", "ratio",
193
- "attack", "release"};
194
- return audio_node_build(NULL, params, 5);
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 = (int)xt_to_number(xt_arg(argc, argv, 0));
202
- int length = (int)xt_to_number(xt_arg(argc, argv, 1));
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
- xt_value array = xt_array_new(0, NULL);
207
- for (int index = 0; index < channels; index++) {
208
- xt_value size = xt_number((double)length);
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
- /** An empty `Float32Array` — the answer for a channel index a buffer does not
223
- * have, and for a buffer nothing filled. */
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
- /* Channels live in an *array* value (`XT_TAG_ARRAY`), which
232
- * `XT_IS_OBJECT` does not match. */
233
- xt_value channels = xt_object_get_cstr(self, "__xt_audio_channels");
234
- int index = (int)xt_to_number(xt_arg(argc, argv, 0));
235
- if (!XT_IS_ARRAY(channels) && !XT_IS_OBJECT(channels)) return empty_channel();
236
- if (index < 0 || index >= xt_array_size(channels)) return empty_channel();
237
- return xt_array_get(channels, xt_number((double)index));
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 ctx_decode_audio_data(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
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
- return xt_promise_resolve(buffer);
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
- set_prop(self, "state", xt_string_from_cstr("running"));
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
- set_prop(self, "state", xt_string_from_cstr("suspended"));
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
- set_prop(self, "state", xt_string_from_cstr("closed"));
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(void) {
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
- (void)argc;
304
- (void)argv;
305
- xt_value context = xt_object_new_with_proto(audio_proto());
306
- xt_value destination = audio_node_build(NULL, NULL, 0);
307
- set_prop(context, "state", xt_string_from_cstr("running"));
308
- set_prop(context, "destination", destination);
309
- set_number(context, "sampleRate", 48000);
310
- set_number(context, "currentTime", 0);
311
- set_number(context, "baseLatency", 0.01);
312
- set_number(context, "outputLatency", 0.01);
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(context, "listener", listener);
315
- return context;
526
+ set_prop(value, "listener", listener);
527
+ return value;
316
528
  }