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.