223 lines
6.2 KiB
JavaScript
Executable File
223 lines
6.2 KiB
JavaScript
Executable File
(function ( w, d, PKAE ) {
|
|
'use strict';
|
|
|
|
var PKREC = function ( app ) {
|
|
var q = this;
|
|
|
|
var media_stream_source = null;
|
|
var audio_stream = null;
|
|
var audio_context = null;
|
|
var script_processor = null;
|
|
|
|
var buffer_size = 2048 * 2; // * 2 ?
|
|
var channel_num = 1;
|
|
var channel_num_out = 1;
|
|
|
|
var is_active = false;
|
|
|
|
var starting_offset = 0;
|
|
var ending_offset = 0;
|
|
|
|
var sample_rate = 0;
|
|
|
|
var temp_buffers = [];
|
|
var temp_buffer_index = -1;
|
|
var jumps = 1;
|
|
|
|
var end_record_func = null;
|
|
var start_record_func = null;
|
|
|
|
// temp vars
|
|
var curr_offset = 0;
|
|
var first_skip = 8; // skip first samples to evade the button's click
|
|
var fetchBufferFunction = function( ev ) {
|
|
|
|
if (first_skip > 0) {
|
|
--first_skip;
|
|
return ;
|
|
}
|
|
|
|
curr_offset += ev.inputBuffer.duration * sample_rate;
|
|
if (ending_offset <= curr_offset)
|
|
{
|
|
ending_offset > 0 && q.stop ();
|
|
return ;
|
|
}
|
|
|
|
var float_array = ev.inputBuffer.getChannelData (0).slice (0);
|
|
temp_buffers[ ++temp_buffer_index ] = float_array;
|
|
|
|
if (--jumps === 0)
|
|
{
|
|
requestAnimationFrame(function(){
|
|
jumps = 4;
|
|
app.engine.wavesurfer.DrawTemp ( starting_offset, temp_buffers );
|
|
});
|
|
}
|
|
};
|
|
|
|
this.isActive = function () {
|
|
return (is_active);
|
|
};
|
|
|
|
this.setEndingOffset = function ( ending_offset_seconds ) {
|
|
ending_offset = ending_offset_seconds; // #### * 100
|
|
};
|
|
|
|
this.start = function ( _at_offset, _end_callback, _start_callback, _sample_rate ) {
|
|
if (is_active) return (false);
|
|
if (!navigator.mediaDevices)
|
|
{
|
|
app.fireEvent ('ErrorRec');
|
|
app.fireEvent ('ShowError', 'No recording device found');
|
|
return (false);
|
|
}
|
|
|
|
starting_offset = _at_offset / 1;
|
|
if (isNaN (starting_offset) || !starting_offset) starting_offset = 0;
|
|
curr_offset = starting_offset;
|
|
|
|
audio_context = app.engine.wavesurfer.backend.getAudioContext ();
|
|
if (!audio_context)
|
|
{
|
|
app.fireEvent ('ErrorRec');
|
|
app.fireEvent ('ShowError', 'No recording device found');
|
|
return (false);
|
|
}
|
|
|
|
if (audio_context.currentTime === 0) {
|
|
app.engine.wavesurfer.backend.source.start (0);
|
|
app.engine.wavesurfer.backend.source.stop (0);
|
|
}
|
|
|
|
if (!_sample_rate)
|
|
{
|
|
if (app.engine.wavesurfer.backend.buffer) {
|
|
sample_rate = app.engine.wavesurfer.backend.buffer.sampleRate;
|
|
}
|
|
else {
|
|
sample_rate = audio_context.sampleRate;
|
|
}
|
|
}
|
|
|
|
end_record_func = function (offset, buffers, _callback) {
|
|
async function downsampleAudioBuffer(buffers, sourceSampleRate, targetSampleRate) {
|
|
// Step 1: Concatenate the Float32Array chunks
|
|
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
|
|
const concatenated = new Float32Array(totalLength);
|
|
let offset = 0;
|
|
for (let i = 0; i < buffers.length; i++) {
|
|
concatenated.set(buffers[i], offset);
|
|
offset += buffers[i].length;
|
|
}
|
|
|
|
// Step 2: Create an AudioBuffer from the concatenated data at the source sample rate
|
|
// Create a temporary AudioContext to build the AudioBuffer
|
|
const tempCtx = new AudioContext({ sampleRate: sourceSampleRate });
|
|
const audioBuffer = tempCtx.createBuffer(1, totalLength, sourceSampleRate);
|
|
audioBuffer.copyToChannel(concatenated, 0, 0);
|
|
|
|
// Release the temporary context if you don't need it anymore
|
|
tempCtx.close();
|
|
|
|
// Step 3: Use an OfflineAudioContext to resample the AudioBuffer to the target sample rate
|
|
const duration = audioBuffer.duration;
|
|
const newLength = Math.ceil(duration * targetSampleRate);
|
|
const offlineCtx = new OfflineAudioContext(1, newLength, targetSampleRate);
|
|
|
|
const source = offlineCtx.createBufferSource();
|
|
source.buffer = audioBuffer;
|
|
source.connect(offlineCtx.destination);
|
|
source.start(0);
|
|
|
|
// Render the resampled AudioBuffer
|
|
const renderedBuffer = await offlineCtx.startRendering();
|
|
|
|
// Return the downsampled Float32Array
|
|
return renderedBuffer.getChannelData(0);
|
|
}
|
|
|
|
var source_sample_rate = audio_context ? audio_context.sampleRate : 48000;
|
|
if (source_sample_rate === sample_rate) {
|
|
_callback ();
|
|
_end_callback (offset, buffers);
|
|
return ;
|
|
}
|
|
|
|
downsampleAudioBuffer(buffers, source_sample_rate, sample_rate).then(newBuffer => {
|
|
_callback ();
|
|
_end_callback (offset, [newBuffer]);
|
|
}).catch(error => {
|
|
_callback ();
|
|
console.error("Error during downsampling:", error);
|
|
});
|
|
|
|
// _end_callback (offset, buffers);
|
|
};
|
|
start_record_func = _start_callback;
|
|
|
|
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function( stream ) {
|
|
audio_stream = stream;
|
|
media_stream_source = audio_context.createMediaStreamSource ( stream );
|
|
|
|
script_processor = audio_context.createScriptProcessor (
|
|
buffer_size, channel_num, channel_num_out
|
|
);
|
|
|
|
media_stream_source.connect ( script_processor );
|
|
script_processor.connect ( audio_context.destination );
|
|
|
|
is_active = true;
|
|
start_record_func && start_record_func ();
|
|
|
|
script_processor.onaudioprocess = fetchBufferFunction;
|
|
}).catch(function(error) {
|
|
app.fireEvent ('ErrorRec');
|
|
|
|
if (error && error.message)
|
|
{
|
|
app.fireEvent ('ShowError', error.message);
|
|
}
|
|
});
|
|
|
|
return (true);
|
|
};
|
|
|
|
this.stop = function ( cancel_recording ) {
|
|
if (!is_active) return ;
|
|
|
|
// fire one last callback to clean temp_buffers?
|
|
audio_stream.getTracks().forEach(function (stream) {
|
|
stream.stop ();
|
|
});
|
|
|
|
script_processor.onaudioprocess = null;
|
|
media_stream_source.disconnect ();
|
|
script_processor.disconnect ();
|
|
|
|
app.engine.wavesurfer.DrawTemp ( null );
|
|
|
|
if (temp_buffers.length > 0 && !cancel_recording)
|
|
end_record_func && end_record_func ( starting_offset / sample_rate, temp_buffers, function (){
|
|
is_active = false;
|
|
});
|
|
else
|
|
end_record_func && end_record_func ( null, null, function(){
|
|
is_active = false;
|
|
});
|
|
|
|
sample_rate = 0;
|
|
first_skip = 8;
|
|
jumps = 1;
|
|
temp_buffer_index = -1;
|
|
starting_offset = ending_offset = 0;
|
|
temp_buffers = [];
|
|
audio_stream = null; audio_context = null;
|
|
end_record_func = start_record_func = null;
|
|
};
|
|
// ---
|
|
};
|
|
|
|
PKAE._deps.rec = PKREC;
|
|
|
|
})( window, document, PKAudioEditor ); |