Initial commit: ACE-Step UI - Open source music generation interface
This commit is contained in:
Executable
+223
@@ -0,0 +1,223 @@
|
||||
(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 );
|
||||
Reference in New Issue
Block a user