Hello everyone,
I am having difficulties mixing two MP3 audio files in my Bubble.io application using the Toolbox plugin and JavaScript. Here is what I have done so far:
- Configuration of “File Uploader” Elements:
- I have added two “File Uploader” elements with IDs
audio1
andaudio2
to upload the audio files (voice and music). - The files are correctly loaded into these elements.
- Added a Mixing Button:
- I added a button that starts the mixing process when clicked.
- Added an Audio Player and a Download Link:
- I added HTML elements to display the audio player and the download link:
- Using “JavascriptToBubble”:
- I added the “JavascriptToBubble” element with the suffix
mixedAudioUrl
.
- JavaScript Script to Mix Audio Files:
- I configured a “Run JavaScript” action in the Toolbox plugin with the following script:
async function mixAudioFiles() {
try {
const voiceFileInput = document.getElementById('audio1').querySelector('input[type="file"]');
const musicFileInput = document.getElementById('audio2').querySelector('input[type="file"]');
if (!voiceFileInput || !musicFileInput) {
alert('Audio file upload elements not found.');
return;
}
const voiceFile = voiceFileInput.files[0];
const musicFile = musicFileInput.files[0];
if (!voiceFile || !musicFile) {
alert('Please upload both audio files.');
return;
}
const voiceArrayBuffer = await voiceFile.arrayBuffer();
const musicArrayBuffer = await musicFile.arrayBuffer();
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const voiceBuffer = await audioContext.decodeAudioData(voiceArrayBuffer);
const musicBuffer = await audioContext.decodeAudioData(musicArrayBuffer);
const outputBuffer = audioContext.createBuffer(
2,
voiceBuffer.length,
audioContext.sampleRate
);
for (let channel = 0; channel < 2; channel++) {
const outputData = outputBuffer.getChannelData(channel);
const voiceData = voiceBuffer.getChannelData(channel % voiceBuffer.numberOfChannels);
const musicData = musicBuffer.getChannelData(channel % musicBuffer.numberOfChannels);
for (let i = 0; i < voiceBuffer.length; i++) {
const musicIndex = Math.floor(i * (musicBuffer.length / voiceBuffer.length));
outputData[i] = voiceData[i] + musicData[musicIndex];
}
}
const mixedAudioBlob = await bufferToWaveBlob(outputBuffer, audioContext.sampleRate);
const mixedAudioUrl = URL.createObjectURL(mixedAudioBlob);
// Pass the mixed audio URL to Bubble via JavascriptToBubble
bubble_fn_mixedAudioUrl(mixedAudioUrl);
} catch (error) {
console.error('Error mixing audio files:', error);
alert('An error occurred while mixing the audio files. Please try again.');
}
}
function bufferToWaveBlob(buffer, sampleRate) {
return new Promise(resolve => {
const worker = new Worker(URL.createObjectURL(new Blob([`
self.onmessage = function(e) {
const buffer = e.data.buffer;
const sampleRate = e.data.sampleRate;
const length = buffer.length;
const numOfChan = buffer.numberOfChannels;
const bufferLength = length * numOfChan * 2 + 44;
const result = new ArrayBuffer(bufferLength);
const view = new DataView(result);
let offset = 0;
const writeString = function(view, offset, string) {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
};
writeString(view, offset, 'RIFF'); offset += 4;
view.setUint32(offset, 36 + length * 2 * numOfChan, true); offset += 4;
writeString(view, offset, 'WAVE'); offset += 4;
writeString(view, offset, 'fmt '); offset += 4;
view.setUint32(offset, 16, true); offset += 4;
view.setUint16(offset, 1, true); offset += 2;
view.setUint16(offset, numOfChan, true); offset += 2;
view.setUint32(offset, sampleRate, true); offset += 4;
view.setUint32(offset, sampleRate * 2 * numOfChan, true); offset += 4;
view.setUint16(offset, numOfChan * 2, true); offset += 2;
view.setUint16(offset, 16, true); offset += 2;
writeString(view, offset, 'data'); offset += 4;
view.setUint32(offset, length * 2 * numOfChan, true); offset += 4;
for (let i = 0; i < length; i++) {
for (let channel = 0; channel < numOfChan; channel++) {
const sample = buffer.getChannelData(channel)[i];
view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
offset += 2;
}
}
self.postMessage(result, [result]);
}
`])));
worker.onmessage = function(e) {
resolve(new Blob([e.data], { type: 'audio/wav' }));
};
worker.postMessage({ buffer, sampleRate });
});
}
// Call the mixAudioFiles function
mixAudioFiles();
- Workflow to Update the Audio Player URL and Download Link:
- I configured a workflow for the event “When a JavascriptToBubble mixedAudioUrl is fired” with the following action:
const mixedAudioUrl = bubble_fn_mixedAudioUrl;
document.getElementById('mixedAudio').src = mixedAudioUrl;
document.getElementById('downloadLink').href = mixedAudioUrl;
document.getElementById('downloadLink').style.display = 'inline';
Issue Encountered
When I try to mix the audio files, Bubble keeps showing “Please upload both audio files” even though the files are correctly selected in the “File Uploader” elements.
I am not sure why the files are not being correctly retrieved. Do you have any suggestions or advice to resolve this issue?
Thank you in advance for your help !