stygian_browser/
audio_noise.rs1use crate::noise::NoiseEngine;
20
21#[must_use]
43pub fn audio_noise_script(engine: &NoiseEngine) -> String {
44 let noise_fn = engine.js_noise_fn();
45 format!(
46 r"(function() {{
47 'use strict';
48
49 // ── Noise helpers ──────────────────────────────────────────────────────
50 {noise_fn}
51
52 // ── Spoof toString ─────────────────────────────────────────────────────
53 function _nts(name) {{ return function toString() {{ return 'function ' + name + '() {{ [native code] }}'; }}; }}
54 function _def(obj, prop, fn) {{
55 fn.toString = _nts(prop);
56 Object.defineProperty(obj, prop, {{ value: fn, writable: false, configurable: false, enumerable: false }});
57 }}
58
59 // ── AudioBuffer.getChannelData ───────────────────────────────────────
60 if (typeof AudioBuffer !== 'undefined') {{
61 const _origGCD = AudioBuffer.prototype.getChannelData;
62 _def(AudioBuffer.prototype, 'getChannelData', function getChannelData(channel) {{
63 const data = _origGCD.call(this, channel);
64 const key = 'audio.getChannelData.' + channel;
65 for (let i = 0; i < data.length; i++) {{
66 data[i] += __stygian_float_noise(key, i);
67 }}
68 return data;
69 }});
70
71 // AudioBuffer.copyFromChannel
72 const _origCFC = AudioBuffer.prototype.copyFromChannel;
73 _def(AudioBuffer.prototype, 'copyFromChannel', function copyFromChannel(dest, channelNumber, startInChannel) {{
74 const off = startInChannel || 0;
75 _origCFC.call(this, dest, channelNumber, off);
76 const key = 'audio.copyFromChannel.' + channelNumber;
77 for (let i = 0; i < dest.length; i++) {{
78 dest[i] += __stygian_float_noise(key, off + i);
79 }}
80 }});
81 }}
82
83 // ── AnalyserNode frequency/time domain ──────────────────────────────
84 if (typeof AnalyserNode !== 'undefined') {{
85 const _origGFFD = AnalyserNode.prototype.getFloatFrequencyData;
86 _def(AnalyserNode.prototype, 'getFloatFrequencyData', function getFloatFrequencyData(arr) {{
87 _origGFFD.call(this, arr);
88 for (let i = 0; i < arr.length; i++) {{
89 arr[i] += __stygian_float_noise('audio.floatFreq', i);
90 }}
91 }});
92
93 const _origGBFD = AnalyserNode.prototype.getByteFrequencyData;
94 _def(AnalyserNode.prototype, 'getByteFrequencyData', function getByteFrequencyData(arr) {{
95 _origGBFD.call(this, arr);
96 for (let i = 0; i < arr.length; i++) {{
97 const delta = (__stygian_float_noise('audio.byteFreq', i) * 1e5) | 0;
98 arr[i] = Math.max(0, Math.min(255, arr[i] + delta));
99 }}
100 }});
101
102 const _origGFTD = AnalyserNode.prototype.getFloatTimeDomainData;
103 _def(AnalyserNode.prototype, 'getFloatTimeDomainData', function getFloatTimeDomainData(arr) {{
104 _origGFTD.call(this, arr);
105 for (let i = 0; i < arr.length; i++) {{
106 arr[i] += __stygian_float_noise('audio.floatTime', i);
107 }}
108 }});
109
110 const _origGBTD = AnalyserNode.prototype.getByteTimeDomainData;
111 _def(AnalyserNode.prototype, 'getByteTimeDomainData', function getByteTimeDomainData(arr) {{
112 _origGBTD.call(this, arr);
113 for (let i = 0; i < arr.length; i++) {{
114 const delta = (__stygian_float_noise('audio.byteTime', i) * 1e5) | 0;
115 arr[i] = Math.max(0, Math.min(255, arr[i] + delta));
116 }}
117 }});
118 }}
119
120 // ── OfflineAudioContext.startRendering ───────────────────────────────
121 if (typeof OfflineAudioContext !== 'undefined') {{
122 const _origSR = OfflineAudioContext.prototype.startRendering;
123 _def(OfflineAudioContext.prototype, 'startRendering', function startRendering() {{
124 return _origSR.call(this).then(function(buffer) {{
125 const nCh = buffer.numberOfChannels;
126 for (let c = 0; c < nCh; c++) {{
127 const data = buffer.getChannelData(c);
128 const key = 'audio.offline.' + c;
129 for (let i = 0; i < data.length; i++) {{
130 data[i] += __stygian_float_noise(key, i);
131 }}
132 }}
133 return buffer;
134 }});
135 }});
136 }}
137
138}})();
139"
140 )
141}
142
143#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::noise::{NoiseEngine, NoiseSeed};
151
152 fn eng(seed: u64) -> NoiseEngine {
153 NoiseEngine::new(NoiseSeed::from(seed))
154 }
155
156 #[test]
157 fn script_overrides_all_methods() {
158 let js = audio_noise_script(&eng(1));
159 assert!(js.contains("getChannelData"), "missing getChannelData");
160 assert!(js.contains("copyFromChannel"), "missing copyFromChannel");
161 assert!(
162 js.contains("getFloatFrequencyData"),
163 "missing getFloatFrequencyData"
164 );
165 assert!(
166 js.contains("getByteFrequencyData"),
167 "missing getByteFrequencyData"
168 );
169 assert!(
170 js.contains("getFloatTimeDomainData"),
171 "missing getFloatTimeDomainData"
172 );
173 assert!(
174 js.contains("getByteTimeDomainData"),
175 "missing getByteTimeDomainData"
176 );
177 assert!(js.contains("startRendering"), "missing startRendering");
178 }
179
180 #[test]
181 fn script_contains_float_noise_fn() {
182 let js = audio_noise_script(&eng(1));
183 assert!(
184 js.contains("__stygian_float_noise"),
185 "missing __stygian_float_noise"
186 );
187 }
188
189 #[test]
190 fn script_contains_native_tostring() {
191 let js = audio_noise_script(&eng(1));
192 assert!(js.contains("[native code]"), "missing toString spoof");
193 }
194
195 #[test]
196 fn script_contains_seed() {
197 let js = audio_noise_script(&eng(54321));
198 assert!(js.contains("54321"), "seed not embedded");
199 }
200
201 #[test]
202 fn different_seeds_differ() {
203 let js1 = audio_noise_script(&eng(1));
204 let js2 = audio_noise_script(&eng(2));
205 assert_ne!(js1, js2);
206 }
207}