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
@@ -0,0 +1,238 @@
1
+ /*
2
+ * xbintsc GUI engine — the Web Audio mixer.
3
+ *
4
+ * `audio.cpp` owns the JavaScript-facing objects (AudioContext, AudioNode,
5
+ * AudioParam, AudioBuffer); this unit owns the graph those objects describe and
6
+ * turns it into interleaved 48 kHz stereo float PCM. It knows nothing about
7
+ * `xt_value`, the DOM or the garbage collector, which is what lets the SDL
8
+ * audio callback render on its own thread without touching a runtime value.
9
+ *
10
+ * Rendering follows the Web Audio model: 128-frame render quanta, multiple
11
+ * inputs summed at every node, and an AudioParam timeline evaluated per sample.
12
+ * A node's quantum is cached for the quantum serial, so a node feeding two
13
+ * destinations (fan-out) is computed once and a cycle renders silence instead
14
+ * of recursing forever.
15
+ *
16
+ * Threading: the graph is mutated from the page thread (create/connect/start)
17
+ * and read from the SDL audio thread (render). Every public method takes the
18
+ * same mutex, so a mutation never interleaves with a render. No callback ever
19
+ * touches an `xt_value`.
20
+ */
21
+ #ifndef XT_GUI_AUDIO_MIXER_H
22
+ #define XT_GUI_AUDIO_MIXER_H
23
+
24
+ #include <cstdint>
25
+ #include <memory>
26
+ #include <mutex>
27
+ #include <unordered_map>
28
+ #include <vector>
29
+
30
+ struct SDL_AudioStream;
31
+
32
+ namespace xtgui {
33
+
34
+ /** Mix rate of every context (SDL converts to the device rate). */
35
+ constexpr int kAudioMixRate = 48000;
36
+ constexpr int kAudioMixChannels = 2;
37
+ /** Web Audio's render quantum, in frames. */
38
+ constexpr int kAudioQuantum = 128;
39
+
40
+ enum AudioNodeKind {
41
+ kAudioDestination,
42
+ kAudioGain,
43
+ kAudioOscillator,
44
+ kAudioBufferSource,
45
+ kAudioBiquad,
46
+ kAudioCompressor,
47
+ kAudioStereoPanner,
48
+ kAudioConstantSource,
49
+ kAudioDelay,
50
+ };
51
+
52
+ enum AudioOscType { kAudioOscSine, kAudioOscSquare, kAudioOscSawtooth, kAudioOscTriangle };
53
+
54
+ enum AudioBiquadType {
55
+ kAudioBiquadLowpass,
56
+ kAudioBiquadHighpass,
57
+ kAudioBiquadBandpass,
58
+ kAudioBiquadLowshelf,
59
+ kAudioBiquadHighshelf,
60
+ kAudioBiquadPeaking,
61
+ kAudioBiquadNotch,
62
+ kAudioBiquadAllpass,
63
+ };
64
+
65
+ /** One decoded/created AudioBuffer's samples, in planar float.
66
+ *
67
+ * A buffer source takes a reference to one of these when it starts. Decoding
68
+ * (or an AudioBuffer handed to `start`) copies the samples once, so playback
69
+ * never reads a runtime value and a buffer can be collected while it plays. */
70
+ struct AudioClip {
71
+ std::vector<std::vector<float>> channels;
72
+ int sample_rate = kAudioMixRate;
73
+ uint32_t length = 0;
74
+
75
+ double duration() const {
76
+ return sample_rate > 0 ? static_cast<double>(length) / sample_rate : 0.0;
77
+ }
78
+ };
79
+
80
+ /** One AudioParam: a base value plus the scheduled automation events.
81
+ *
82
+ * Events are evaluated on demand at a sample time, which keeps scheduling and
83
+ * rendering independent: a page may schedule seconds ahead, and a source may
84
+ * start with a nonzero `when`. */
85
+ class AudioParamState {
86
+ public:
87
+ void reset(double value);
88
+ /** The `value` setter: takes effect at `time` and overrides older events. */
89
+ void set_value(double value, double time);
90
+ void set_value_at_time(double value, double time);
91
+ void linear_ramp(double value, double time);
92
+ void exponential_ramp(double value, double time);
93
+ void set_target(double value, double time, double time_constant);
94
+ void cancel_scheduled(double time);
95
+ void cancel_and_hold(double time);
96
+ double value_at(double time) const;
97
+
98
+ private:
99
+ enum EventKind { kEventSet, kEventLinear, kEventExponential, kEventTarget };
100
+ struct Event {
101
+ double time;
102
+ EventKind kind;
103
+ double value;
104
+ double time_constant;
105
+ };
106
+
107
+ double evaluate(size_t index, double time) const;
108
+ void insert(Event event);
109
+
110
+ double base_ = 0.0;
111
+ std::vector<Event> events_;
112
+ };
113
+
114
+ /** One graph node. Only the fields a node's kind uses are meaningful. */
115
+ struct AudioNode {
116
+ uint64_t id = 0;
117
+ AudioNodeKind kind = kAudioGain;
118
+ std::vector<uint64_t> inputs;
119
+ AudioParamState params[5];
120
+ int param_count = 0;
121
+
122
+ /* Per-quantum cache: 2 * kAudioQuantum interleaved floats. */
123
+ std::vector<float> quantum;
124
+ uint64_t serial = 0;
125
+ bool rendering = false;
126
+
127
+ /* Oscillator. */
128
+ AudioOscType osc_type = kAudioOscSine;
129
+ double phase = 0.0;
130
+
131
+ /* Sources (oscillator and buffer). */
132
+ bool started = false;
133
+ bool began = false;
134
+ bool ended = false;
135
+ double start_time = 0.0;
136
+ double stop_time = -1.0;
137
+ std::shared_ptr<AudioClip> clip;
138
+ double clip_offset = 0.0;
139
+ double clip_duration = 0.0;
140
+ double clip_pos = 0.0;
141
+ bool loop = false;
142
+ double loop_start = 0.0;
143
+ double loop_end = 0.0;
144
+
145
+ /* Biquad filter state, one set per channel. */
146
+ AudioBiquadType biquad_type = kAudioBiquadLowpass;
147
+ double x1[2] = {0.0, 0.0};
148
+ double x2[2] = {0.0, 0.0};
149
+ double y1[2] = {0.0, 0.0};
150
+ double y2[2] = {0.0, 0.0};
151
+
152
+ /* Compressor envelope, in dB of gain reduction. */
153
+ double compressor_env = 0.0;
154
+
155
+ /* Delay line, one ring per channel. `delay_size` is 0 until the JS factory
156
+ * has supplied `maxDelayTime`. */
157
+ std::vector<float> delay[2];
158
+ size_t delay_size = 0;
159
+ size_t delay_write = 0;
160
+ double delay_max = 1.0;
161
+ };
162
+
163
+ /** One AudioContext's graph, its device stream and its clock. */
164
+ class AudioMixer {
165
+ public:
166
+ explicit AudioMixer(bool offline);
167
+ ~AudioMixer();
168
+
169
+ AudioMixer(const AudioMixer &) = delete;
170
+ AudioMixer &operator=(const AudioMixer &) = delete;
171
+
172
+ bool offline() const { return offline_; }
173
+
174
+ /* -- graph ------------------------------------------------------------- */
175
+ uint64_t create_node(AudioNodeKind kind);
176
+ void connect(uint64_t source, uint64_t target);
177
+ void disconnect(uint64_t source, uint64_t target);
178
+ void disconnect_all(uint64_t source);
179
+ void set_osc_type(uint64_t node, AudioOscType type);
180
+ void set_biquad_type(uint64_t node, AudioBiquadType type);
181
+ /** Allocate a delay line long enough for `seconds` (constructor argument of
182
+ * `createDelay`). */
183
+ void set_delay_max(uint64_t node, double seconds);
184
+ void set_buffer(uint64_t node, std::shared_ptr<AudioClip> clip, bool loop, double loop_start,
185
+ double loop_end);
186
+ void start_source(uint64_t node, double when, double offset, double duration);
187
+ void stop_source(uint64_t node, double when);
188
+
189
+ /* -- AudioParam -------------------------------------------------------- */
190
+ double param_value(uint64_t node, int index);
191
+ void param_set(uint64_t node, int index, double value);
192
+ void param_set_at(uint64_t node, int index, double value, double time);
193
+ void param_linear_ramp(uint64_t node, int index, double value, double time);
194
+ void param_exponential_ramp(uint64_t node, int index, double value, double time);
195
+ void param_set_target(uint64_t node, int index, double value, double time, double time_constant);
196
+ void param_cancel(uint64_t node, int index, double time, bool hold);
197
+
198
+ /* -- clock and device -------------------------------------------------- */
199
+ double current_time();
200
+ /** Best effort: a headless host has no device and playback stays silent. */
201
+ bool start_device();
202
+ void suspend_device();
203
+ void resume_device();
204
+ void close_device();
205
+ bool has_device();
206
+ /** Drop the graph; called by `AudioContext.close()`. */
207
+ void shutdown();
208
+
209
+ /** Render `frames` stereo frames (interleaved) from the graph. Safe to call
210
+ * from the audio callback and from `render_offline`. */
211
+ void render(float *out, int frames);
212
+ /** Synchronous render for the offline diagnostic hook: no device involved. */
213
+ void render_offline(float *out, int frames);
214
+
215
+ private:
216
+ AudioNode *node_locked(uint64_t id);
217
+ const float *pull_locked(AudioNode *node);
218
+ void render_quantum_locked();
219
+ void render_locked(float *out, int frames);
220
+
221
+ std::mutex mutex_;
222
+ std::unordered_map<uint64_t, std::shared_ptr<AudioNode>> nodes_;
223
+ uint64_t next_node_id_ = 1;
224
+ uint64_t destination_id_ = 0;
225
+ uint64_t serial_ = 0;
226
+ uint64_t frames_rendered_ = 0;
227
+ std::vector<float> pending_;
228
+ size_t pending_offset_ = 0;
229
+
230
+ const bool offline_;
231
+ bool running_ = true;
232
+ SDL_AudioStream *stream_ = nullptr;
233
+ bool device_tried_ = false;
234
+ };
235
+
236
+ } // namespace xtgui
237
+
238
+ #endif /* XT_GUI_AUDIO_MIXER_H */
@@ -0,0 +1,104 @@
1
+ /*
2
+ * xbintsc GUI engine — AudioParam automation curves.
3
+ *
4
+ * A param is a base value plus a time-ordered event list. Every accessor on the
5
+ * page's AudioParam (value, setValueAtTime, the ramps, setTargetAtTime,
6
+ * cancel*) appends an event; the mixer evaluates the list per sample so the
7
+ * curve keeps moving even when no JS calls happen between frames.
8
+ *
9
+ * The timeline belongs to the mixer and is only reached with the mixer's lock
10
+ * held, so it needs no lock of its own.
11
+ */
12
+
13
+ #include "audio_mixer.h"
14
+
15
+ #include <cmath>
16
+
17
+ namespace xtgui {
18
+
19
+ void AudioParamState::reset(double value) {
20
+ base_ = value;
21
+ events_.clear();
22
+ }
23
+
24
+ void AudioParamState::insert(Event event) {
25
+ for (size_t i = 0; i < events_.size(); i++) {
26
+ if (std::fabs(events_[i].time - event.time) < 1e-12) {
27
+ events_[i] = event;
28
+ return;
29
+ }
30
+ if (events_[i].time > event.time) {
31
+ events_.insert(events_.begin() + static_cast<std::ptrdiff_t>(i), event);
32
+ return;
33
+ }
34
+ }
35
+ events_.push_back(event);
36
+ }
37
+
38
+ void AudioParamState::set_value(double value, double time) {
39
+ if (events_.empty()) base_ = value;
40
+ insert({time, kEventSet, value, 0.0});
41
+ }
42
+
43
+ void AudioParamState::set_value_at_time(double value, double time) {
44
+ insert({time, kEventSet, value, 0.0});
45
+ }
46
+
47
+ void AudioParamState::linear_ramp(double value, double time) {
48
+ insert({time, kEventLinear, value, 0.0});
49
+ }
50
+
51
+ void AudioParamState::exponential_ramp(double value, double time) {
52
+ insert({time, kEventExponential, value, 0.0});
53
+ }
54
+
55
+ void AudioParamState::set_target(double value, double time, double time_constant) {
56
+ insert({time, kEventTarget, value, time_constant});
57
+ }
58
+
59
+ void AudioParamState::cancel_scheduled(double time) {
60
+ while (!events_.empty() && events_.back().time >= time) events_.pop_back();
61
+ }
62
+
63
+ void AudioParamState::cancel_and_hold(double time) {
64
+ double value = value_at(time);
65
+ cancel_scheduled(time);
66
+ insert({time, kEventSet, value, 0.0});
67
+ }
68
+
69
+ double AudioParamState::evaluate(size_t index, double time) const {
70
+ const Event &event = events_[index];
71
+ if (event.kind == kEventSet) return event.value;
72
+ if (event.kind == kEventTarget) {
73
+ double start = index > 0 ? evaluate(index - 1, events_[index - 1].time) : base_;
74
+ if (event.time_constant <= 0.0) return event.value;
75
+ double decay = std::exp(-(time - event.time) / event.time_constant);
76
+ return event.value + (start - event.value) * decay;
77
+ }
78
+ /* A ramp that has already ended holds its target value. */
79
+ return event.value;
80
+ }
81
+
82
+ double AudioParamState::value_at(double time) const {
83
+ if (events_.empty() || time < events_.front().time) return base_;
84
+ size_t index = 0;
85
+ while (index + 1 < events_.size() && events_[index + 1].time <= time) index++;
86
+ if (index + 1 < events_.size()) {
87
+ const Event &next = events_[index + 1];
88
+ if ((next.kind == kEventLinear || next.kind == kEventExponential) && time < next.time) {
89
+ double start_time = events_[index].time;
90
+ double start_value = evaluate(index, start_time);
91
+ double span = next.time - start_time;
92
+ double fraction = span > 0.0 ? (time - start_time) / span : 1.0;
93
+ if (next.kind == kEventLinear) {
94
+ return start_value + (next.value - start_value) * fraction;
95
+ }
96
+ double from = std::fabs(start_value) < 1e-9 ? 1e-6 : start_value;
97
+ double to = std::fabs(next.value) < 1e-9 ? 1e-6 : next.value;
98
+ return from * std::pow(to / from, fraction);
99
+ }
100
+ }
101
+ return evaluate(index, time);
102
+ }
103
+
104
+ } // namespace xtgui
@@ -0,0 +1,155 @@
1
+ /*
2
+ * xbintsc GUI engine — the JavaScript AudioParam object.
3
+ *
4
+ * The automation timeline itself lives in the mixer (`audio_param.cpp`); this
5
+ * unit is the object a page holds: a live `value` accessor plus the scheduling
6
+ * methods, each forwarding to the native node slot it names. Splitting it out of
7
+ * `audio.cpp` keeps both translation units within the file-size budget.
8
+ */
9
+
10
+ #include "audio_internal.h"
11
+
12
+ #include <cstdint>
13
+ #include <memory>
14
+
15
+ namespace {
16
+
17
+ using namespace xtgui;
18
+
19
+ double number_prop(xt_value object, const char *name) {
20
+ xt_value value = xt_object_get_cstr(object, name);
21
+ return XT_IS_NUMBER(value) ? xt_to_number(value) : 0.0;
22
+ }
23
+
24
+ xt_value fn_of(void *fn) { return xt_closure_new(fn, 0, NULL); }
25
+
26
+ struct ParamRef {
27
+ std::shared_ptr<AudioMixer> mixer;
28
+ uint64_t node = 0;
29
+ int index = 0;
30
+ };
31
+
32
+ bool param_ref(xt_value object, ParamRef *out) {
33
+ out->mixer = audio_mixer_by_id(audio_context_id(object));
34
+ out->node = static_cast<uint64_t>(number_prop(object, "__xt_audio_node"));
35
+ out->index = static_cast<int>(number_prop(object, "__xt_audio_param"));
36
+ return out->mixer != nullptr && out->node != 0;
37
+ }
38
+
39
+ xt_value param_value_get(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
40
+ (void)env;
41
+ (void)argc;
42
+ (void)argv;
43
+ ParamRef ref;
44
+ if (!param_ref(self, &ref)) return xt_number(0);
45
+ return xt_number(ref.mixer->param_value(ref.node, ref.index));
46
+ }
47
+
48
+ xt_value param_value_set(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
49
+ (void)env;
50
+ xt_value value = xt_arg(argc, argv, 0);
51
+ if (!XT_IS_NUMBER(value)) value = xt_number(xt_to_number(value));
52
+ ParamRef ref;
53
+ if (param_ref(self, &ref)) {
54
+ ref.mixer->param_set(ref.node, ref.index, xt_to_number(value));
55
+ }
56
+ return value;
57
+ }
58
+
59
+ xt_value param_set_value_at_time(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
60
+ (void)env;
61
+ ParamRef ref;
62
+ if (param_ref(self, &ref)) {
63
+ double value = xt_to_number(xt_arg(argc, argv, 0));
64
+ double time = argc > 1 ? xt_to_number(argv[1]) : ref.mixer->current_time();
65
+ ref.mixer->param_set_at(ref.node, ref.index, value, time);
66
+ }
67
+ return self;
68
+ }
69
+
70
+ xt_value param_linear_ramp(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
71
+ (void)env;
72
+ ParamRef ref;
73
+ if (param_ref(self, &ref)) {
74
+ double value = xt_to_number(xt_arg(argc, argv, 0));
75
+ double time = argc > 1 ? xt_to_number(argv[1]) : ref.mixer->current_time();
76
+ ref.mixer->param_linear_ramp(ref.node, ref.index, value, time);
77
+ }
78
+ return self;
79
+ }
80
+
81
+ xt_value param_exponential_ramp(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
82
+ (void)env;
83
+ ParamRef ref;
84
+ if (param_ref(self, &ref)) {
85
+ double value = xt_to_number(xt_arg(argc, argv, 0));
86
+ double time = argc > 1 ? xt_to_number(argv[1]) : ref.mixer->current_time();
87
+ ref.mixer->param_exponential_ramp(ref.node, ref.index, value, time);
88
+ }
89
+ return self;
90
+ }
91
+
92
+ xt_value param_set_target(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
93
+ (void)env;
94
+ ParamRef ref;
95
+ if (param_ref(self, &ref)) {
96
+ double value = xt_to_number(xt_arg(argc, argv, 0));
97
+ double time = argc > 1 ? xt_to_number(argv[1]) : ref.mixer->current_time();
98
+ double constant = argc > 2 ? xt_to_number(argv[2]) : 0.0;
99
+ ref.mixer->param_set_target(ref.node, ref.index, value, time, constant);
100
+ }
101
+ return self;
102
+ }
103
+
104
+ xt_value param_cancel(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
105
+ (void)env;
106
+ ParamRef ref;
107
+ if (param_ref(self, &ref)) {
108
+ double time = argc > 0 ? xt_to_number(argv[0]) : 0.0;
109
+ ref.mixer->param_cancel(ref.node, ref.index, time, false);
110
+ }
111
+ return self;
112
+ }
113
+
114
+ xt_value param_cancel_and_hold(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
115
+ (void)env;
116
+ ParamRef ref;
117
+ if (param_ref(self, &ref)) {
118
+ double time = argc > 0 ? xt_to_number(argv[0]) : 0.0;
119
+ ref.mixer->param_cancel(ref.node, ref.index, time, true);
120
+ }
121
+ return self;
122
+ }
123
+
124
+ void set_prop(xt_value target, const char *name, xt_value value) {
125
+ xt_object_set(target, xt_string_from_cstr(name), value);
126
+ }
127
+
128
+ void set_method(xt_value target, const char *name, void *fn) { set_prop(target, name, fn_of(fn)); }
129
+
130
+ void set_number(xt_value target, const char *name, double value) {
131
+ set_prop(target, name, xt_number(value));
132
+ }
133
+
134
+ } // namespace
135
+
136
+ namespace xtgui {
137
+
138
+ xt_value audio_param_build(uint64_t context, uint64_t node, int index) {
139
+ xt_value param = xt_object_new();
140
+ set_number(param, "__xt_audio_ctx", static_cast<double>(context));
141
+ set_number(param, "__xt_audio_node", static_cast<double>(node));
142
+ set_number(param, "__xt_audio_param", index);
143
+ xt_object_define_getter(param, xt_string_from_cstr("value"), fn_of((void *)param_value_get));
144
+ xt_object_define_setter(param, xt_string_from_cstr("value"), fn_of((void *)param_value_set));
145
+ set_method(param, "setValueAtTime", (void *)param_set_value_at_time);
146
+ set_method(param, "linearRampToValueAtTime", (void *)param_linear_ramp);
147
+ set_method(param, "exponentialRampToValueAtTime", (void *)param_exponential_ramp);
148
+ set_method(param, "setTargetAtTime", (void *)param_set_target);
149
+ set_method(param, "cancelScheduledValues", (void *)param_cancel);
150
+ set_method(param, "cancelAndHoldAtTime", (void *)param_cancel_and_hold);
151
+ set_number(param, "defaultValue", 0.0);
152
+ return param;
153
+ }
154
+
155
+ } // namespace xtgui
@@ -29,7 +29,6 @@ namespace {
29
29
  xt_value g_navigator = XT_UNDEFINED;
30
30
  xt_value g_local_storage = XT_UNDEFINED;
31
31
  xt_value g_location = XT_UNDEFINED;
32
- xt_value g_media_list = XT_UNDEFINED;
33
32
  xt_value g_document_global = XT_UNDEFINED;
34
33
  /** Type name -> the constructor value handed out for it (built on first use). */
35
34
  std::unordered_map<std::string, xt_value> g_types;
@@ -156,35 +155,28 @@ xt_value win_location_get(xt_value self, xt_value env, int32_t argc, xt_value *a
156
155
  return g_location;
157
156
  }
158
157
 
159
- xt_value media_list_build(const std::string &query, bool matches);
160
-
161
- /** `matchMedia(query)` -> a MediaQueryList-shaped object evaluated against the
162
- * current window's viewport, using the same evaluator as `@media` rules. The
163
- * list is a snapshot (there is no live change event), which matches the
164
- * engine's restyle-on-resize model. */
158
+ /** `matchMedia(query)` -> a live `MediaQueryList` evaluated against the current
159
+ * viewport with the same evaluator as `@media` rules. The list is cached per
160
+ * window and query, and `change` listeners fire when a resize flips it. */
165
161
  xt_value win_match_media(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
166
162
  (void)env;
167
163
  std::string query;
168
164
  if (argc >= 1) query = cstr(xt_arg(argc, argv, 0));
169
165
  XtGuiWindow *win = xt_gui_window_from_this(self);
170
- if (win == NULL) {
171
- xt_value handle = xt_gui_ensure_window();
172
- win = xt_gui_window_from_this(handle);
173
- }
174
- float width = 0.0f;
175
- float height = 0.0f;
176
- if (win != NULL) xt_gui_window_viewport(win, &width, &height);
177
- return media_list_build(query, xtgui::xt_css_media_matches(query, width, height));
166
+ if (win == NULL) win = xt_gui_window_from_this(xt_gui_ensure_window());
167
+ if (win == NULL) return xt_object_new();
168
+ return xt_browser_media_list(win->object, query);
178
169
  }
179
170
 
180
- /** `getComputedStyle(el)`: the engine exposes the element's style handle, which
181
- * answers `getPropertyValue` for everything the page set. */
171
+ /** `getComputedStyle(el, pseudoElt)`: a live view of the *cascaded* style, so
172
+ * stylesheet rules (not only the inline `style` attribute) are visible. The
173
+ * optional pseudo-element selector ("::before"/"::after") resolves against the
174
+ * generated-content box; any other pseudo-element yields empty values, like a
175
+ * browser does for one the engine did not generate. */
182
176
  xt_value win_get_computed_style(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
183
177
  (void)self;
184
178
  (void)env;
185
- xt_value element = xt_arg(argc, argv, 0);
186
- xt_value style = xt_get(element, xt_string_from_cstr("style"));
187
- return XT_IS_OBJECT(style) ? style : xt_object_new();
179
+ return domapi::computed_style_handle(xt_arg(argc, argv, 0), xt_arg(argc, argv, 1));
188
180
  }
189
181
 
190
182
  xt_value win_alert(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
@@ -196,19 +188,13 @@ xt_value win_alert(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
196
188
  }
197
189
 
198
190
  xt_value win_confirm(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
199
- (void)self;
200
191
  (void)env;
201
- (void)argc;
202
- (void)argv;
203
- return XT_FALSE;
192
+ return xt_browser_confirm(xt_gui_window_from_this(self), argc, argv);
204
193
  }
205
194
 
206
195
  xt_value win_prompt(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
207
- (void)self;
208
196
  (void)env;
209
- (void)argc;
210
- (void)argv;
211
- return XT_UNDEFINED;
197
+ return xt_browser_prompt(xt_gui_window_from_this(self), argc, argv);
212
198
  }
213
199
 
214
200
  /* -- host objects --------------------------------------------------------- */
@@ -253,18 +239,6 @@ xt_value location_build(void) {
253
239
  return location;
254
240
  }
255
241
 
256
- xt_value media_list_build(const std::string &query, bool matches) {
257
- xt_value list = xt_object_new();
258
- set_prop(list, "media", xt_string_from_cstr(query.c_str()));
259
- set_prop(list, "matches", matches ? XT_TRUE : XT_FALSE);
260
- set_method(list, "addListener", (void *)noop);
261
- set_method(list, "removeListener", (void *)noop);
262
- set_method(list, "addEventListener", (void *)noop);
263
- set_method(list, "removeEventListener", (void *)noop);
264
- set_method(list, "dispatchEvent", (void *)noop);
265
- return list;
266
- }
267
-
268
242
  /* -- localStorage --------------------------------------------------------- */
269
243
 
270
244
  xt_value storage_get_item(xt_value self, xt_value env, int32_t argc, xt_value *argv) {
@@ -338,7 +312,12 @@ xt_value host_type_construct(xt_value self, xt_value env, int32_t argc, xt_value
338
312
  /** The prototype a DOM type name should inherit, so `el instanceof Element`
339
313
  * holds for every element handle the engine hands out. */
340
314
  xt_value type_proto(const std::string &name) {
341
- if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent") {
315
+ if (name == "AbortController") return domapi::abort_controller_proto();
316
+ if (name == "AbortSignal") return domapi::abort_signal_proto();
317
+ if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent" ||
318
+ name == "UIEvent" || name == "MouseEvent" || name == "PointerEvent" ||
319
+ name == "WheelEvent" || name == "KeyboardEvent" || name == "FocusEvent" ||
320
+ name == "InputEvent" || name == "SubmitEvent") {
342
321
  return domapi::event_proto();
343
322
  }
344
323
  if (name == "AudioContext" || name == "webkitAudioContext") return xt_gui_audio_proto();
@@ -357,6 +336,17 @@ xt_value type_proto(const std::string &name) {
357
336
  if (name == "WebGLQuery") return domapi::webgl_object_proto("query");
358
337
  if (name == "WebGLSync") return domapi::webgl_object_proto("sync");
359
338
  if (name == "WebGLTransformFeedback") return domapi::webgl_object_proto("transformfeedback");
339
+ if (name == "Path2D") return domapi::path2d_proto();
340
+ if (name == "ImageBitmap") {
341
+ /* A bitmap is a plain immutable object, not a DOM node: a node proto would
342
+ * expose the element width/height accessors and answer 0. */
343
+ static xt_value bitmap_proto = XT_UNDEFINED;
344
+ if (!XT_IS_OBJECT(bitmap_proto)) {
345
+ bitmap_proto = xt_object_new();
346
+ xt_gc_add_root(&bitmap_proto);
347
+ }
348
+ return bitmap_proto;
349
+ }
360
350
  return domapi::node_proto();
361
351
  }
362
352
 
@@ -384,9 +374,15 @@ xt_value host_type_construct(xt_value self, xt_value env, int32_t argc, xt_value
384
374
  if (name == "AudioContext" || name == "webkitAudioContext") {
385
375
  return xt_gui_audio_create(argc, argv);
386
376
  }
387
- if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent") {
377
+ if (name == "AbortController") return domapi::abort_controller_new();
378
+ if (name == "AbortSignal") return domapi::abort_signal_new(false);
379
+ if (name == "Event" || name == "CustomEvent" || name == "ProgressEvent" ||
380
+ name == "UIEvent" || name == "MouseEvent" || name == "PointerEvent" ||
381
+ name == "WheelEvent" || name == "KeyboardEvent" || name == "FocusEvent" ||
382
+ name == "InputEvent" || name == "SubmitEvent") {
388
383
  return domapi::event_construct(xt_arg(argc, argv, 0), xt_arg(argc, argv, 1));
389
384
  }
385
+ if (name == "Path2D") return domapi::path2d_create(xt_arg(argc, argv, 0));
390
386
  xt_value target = xt_object_new_with_proto(type_proto(name));
391
387
  set_prop(target, "constructor", type_value(name));
392
388
  return target;
@@ -428,10 +424,6 @@ void ensure_singletons(void) {
428
424
  g_location = location_build();
429
425
  xt_gc_add_root(&g_location);
430
426
  }
431
- if (!XT_IS_OBJECT(g_media_list)) {
432
- g_media_list = media_list_build("", false);
433
- xt_gc_add_root(&g_media_list);
434
- }
435
427
  }
436
428
 
437
429
  } // namespace
@@ -464,9 +456,8 @@ void xt_browser_install_window(xt_value proto) {
464
456
  set_method(proto, "confirm", (void *)win_confirm);
465
457
  set_method(proto, "prompt", (void *)win_prompt);
466
458
  set_method(proto, "escape", (void *)xt_browser_escape);
467
- set_method(proto, "focus", (void *)noop);
468
- set_method(proto, "blur", (void *)noop);
469
- set_method(proto, "scrollTo", (void *)noop);
459
+ /* Page scroll and window focus/blur live in `browser_navigation.cpp`. */
460
+ xt_browser_install_window_navigation(proto);
470
461
  }
471
462
 
472
463
  /** `window` / `self`. */
@@ -528,7 +519,7 @@ xt_value xt_browser_type(int32_t argc, xt_value *argv) {
528
519
  if (argc < 1) return XT_UNDEFINED;
529
520
  std::string name = cstr(xt_arg(argc, argv, 0));
530
521
  if (name == "XRWebGLLayer" || name == "XRWebGLBinding" || name == "OffscreenCanvas" ||
531
- name == "ImageBitmap" || name == "ImageData" || name == "Response" ||
522
+ name == "ImageData" || name == "Response" ||
532
523
  name == "ReadableStream" || name == "Request" || name == "Headers" ||
533
524
  name == "DOMParser") {
534
525
  return XT_UNDEFINED;
@@ -571,3 +562,7 @@ xt_value xt_browser_alert(int32_t argc, xt_value *argv) {
571
562
  xt_value xt_browser_prompt(int32_t argc, xt_value *argv) {
572
563
  return win_prompt(XT_UNDEFINED, XT_UNDEFINED, argc, argv);
573
564
  }
565
+
566
+ xt_value xt_browser_confirm_global(int32_t argc, xt_value *argv) {
567
+ return win_confirm(XT_UNDEFINED, XT_UNDEFINED, argc, argv);
568
+ }