PageSourceSearch

https://helle.ai/audio-editor/recorder.js

js helle.ai collected 2026-10-05 12:23:42 UTC 6,343 bytes, 223 lines download raw bytes

1(function ( w, d, PKAE ) {
2	'use strict';
3
4	var PKREC = function ( app ) {
5		var q = this;
6
7		var media_stream_source = null;
8		var audio_stream = null;
9		var audio_context = null;
10		var script_processor = null;
11
12		var buffer_size = 2048 * 2; // * 2 ?
13		var channel_num = 1;
14		var channel_num_out = 1;
15
16		var is_active = false;
17
18		var starting_offset = 0;
19		var ending_offset = 0;
20
21		var sample_rate = 0;
22
23		var temp_buffers = [];
24		var temp_buffer_index = -1;
25		var jumps = 1;
26
27		var end_record_func = null;
28		var start_record_func = null;
29
30		// temp vars
31		var curr_offset = 0;
32		var first_skip = 8; // skip first samples to evade the button's click
33		var fetchBufferFunction = function( ev ) {
34
35			if (first_skip > 0) {
36				--first_skip;
37				return ;
38			}
39
40			curr_offset += ev.inputBuffer.duration * sample_rate;
41			if (ending_offset <= curr_offset)
42			{
43				ending_offset > 0 && q.stop ();
44				return ;
45			}
46
47			var float_array = ev.inputBuffer.getChannelData (0).slice (0);
48			temp_buffers[ ++temp_buffer_index ]  = float_array;
49
50			if (--jumps === 0)
51			{
52				requestAnimationFrame(function(){
53					jumps = 4;
54					app.engine.wavesurfer.DrawTemp ( starting_offset, temp_buffers );
55				});
56			}
57		};
58
59		this.isActive = function () {
60			return (is_active);
61		};
62
63		this.setEndingOffset = function ( ending_offset_seconds ) {
64			ending_offset = ending_offset_seconds; // ####  * 100
65		};
66
67		this.start = function ( _at_offset, _end_callback, _start_callback, _sample_rate ) {
68			if (is_active) return (false);
69			if (!navigator.mediaDevices)
70			{
71				app.fireEvent ('ErrorRec');
72				app.fireEvent ('ShowError', 'No recording device found');
73				return (false);
74			}
75
76			starting_offset = _at_offset / 1;
77			if (isNaN (starting_offset) || !starting_offset) starting_offset = 0;
78			curr_offset = starting_offset;
79
80			audio_context = app.engine.wavesurfer.backend.getAudioContext ();
81			if (!audio_context)
82			{
83				app.fireEvent ('ErrorRec');
84				app.fireEvent ('ShowError', 'No recording device found');
85				return (false);
86			}
87
88			if (audio_context.currentTime === 0) {
89				app.engine.wavesurfer.backend.source.start (0);
90				app.engine.wavesurfer.backend.source.stop (0);
91			}
92
93			if (!_sample_rate)
94			{
95				if (app.engine.wavesurfer.backend.buffer) {
96					sample_rate = app.engine.wavesurfer.backend.buffer.sampleRate;
97				}
98				else {
99					sample_rate = audio_context.sampleRate;
100				}
101			}
102
103			end_record_func = function (offset, buffers, _callback) {
104				async function downsampleAudioBuffer(buffers, sourceSampleRate, targetSampleRate) {
105					// Step 1: Concatenate the Float32Array chunks
106					const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
107					const concatenated = new Float32Array(totalLength);
108					let offset = 0;
109					for (let i = 0; i < buffers.length; i++) {
110						concatenated.set(buffers[i], offset);
111						offset += buffers[i].length;
112					}
113
114					// Step 2: Create an AudioBuffer from the concatenated data at the source sample rate
115					// Create a temporary AudioContext to build the AudioBuffer
116					const tempCtx = new AudioContext({ sampleRate: sourceSampleRate });
117					const audioBuffer = tempCtx.createBuffer(1, totalLength, sourceSampleRate);
118					audioBuffer.copyToChannel(concatenated, 0, 0);
119
120					// Release the temporary context if you don't need it anymore
121					tempCtx.close();
122
123					// Step 3: Use an OfflineAudioContext to resample the AudioBuffer to the target sample rate
124					const duration = audioBuffer.duration;
125					const newLength = Math.ceil(duration * targetSampleRate);
126					const offlineCtx = new OfflineAudioContext(1, newLength, targetSampleRate);
127
128					const source = offlineCtx.createBufferSource();
129					source.buffer = audioBuffer;
130					source.connect(offlineCtx.destination);
131					source.start(0);
132
133					// Render the resampled AudioBuffer
134					const renderedBuffer = await offlineCtx.startRendering();
135
136					// Return the downsampled Float32Array
137					return renderedBuffer.getChannelData(0);
138				}
139
140				var source_sample_rate = audio_context ? audio_context.sampleRate : 48000;
141				if (source_sample_rate === sample_rate) {
142					_callback ();
143					_end_callback (offset, buffers);	
144					return ;
145				}
146
147				downsampleAudioBuffer(buffers, source_sample_rate, sample_rate).then(newBuffer => {
148					_callback ();
149					_end_callback (offset, [newBuffer]);
150				}).catch(error => {
151					_callback ();
152					console.error("Error during downsampling:", error);
153				});
154
155				// _end_callback (offset, buffers);
156			};
157			start_record_func = _start_callback;
158
159			navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function( stream ) {
160				audio_stream = stream;
161				media_stream_source = audio_context.createMediaStreamSource ( stream );
162
163            	script_processor = audio_context.createScriptProcessor (
164                	buffer_size, channel_num, channel_num_out
165                );
166
167            	media_stream_source.connect ( script_processor );
168            	script_processor.connect ( audio_context.destination );
169
170            	is_active = true;
171            	start_record_func && start_record_func ();
172
173            	script_processor.onaudioprocess = fetchBufferFunction;
174			}).catch(function(error) {
175				app.fireEvent ('ErrorRec');
176
177				if (error && error.message)
178				{
179					app.fireEvent ('ShowError', error.message);
180				}
181			});
182
183			return (true);
184		};
185
186		this.stop = function ( cancel_recording ) {
187			if (!is_active) return ;
188
189			// fire one last callback to clean temp_buffers?
190			audio_stream.getTracks().forEach(function (stream) {
191				stream.stop ();
192			});
193
194			script_processor.onaudioprocess = null;
195			media_stream_source.disconnect ();
196			script_processor.disconnect ();
197
198			app.engine.wavesurfer.DrawTemp ( null );
199
200			if (temp_buffers.length > 0 && !cancel_recording)
201				end_record_func && end_record_func ( starting_offset / sample_rate, temp_buffers, function (){
202					is_active = false;
203				});
204			else
205				end_record_func && end_record_func ( null, null, function(){
206					is_active = false;
207				});
208
209			sample_rate = 0;
210			first_skip = 8;
211			jumps = 1;
212			temp_buffer_index = -1;
213			starting_offset = ending_offset = 0;
214			temp_buffers = [];
215			audio_stream = null; audio_context = null;
216			end_record_func = start_record_func = null;
217		};
218		// ---
219	};
220
221	PKAE._deps.rec = PKREC;
222
223})( window, document, PKAudioEditor );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.