Creating a webpage to load audio file and then display spectrogram of the audio file using web audio - web-audio-api

I am creating a webpage to load an audio file from my local drive and then display a spectrogram of the audio file. I am trying to do this using web audio API. However, my code does not run and I always get the following error message.
Any suggestion or solution that you could give is highly appreciated.
Thank you!
<!DOCTYPE html>
<html>
<head>
<title>Spectrogram</title>
</head>
<body>
<h1>Spectrogram</h1>
<form>
<label for="file-input">Select an audio file:</label>
<input type="file" id="file-input" accept="audio/*" />
<input type="submit" value="Submit" />
</form>
<canvas id="spectrogram" width="500" height="200"></canvas>
<script>
const fileInput = document.getElementById('file-input');
const spectrogramCanvas = document.getElementById('spectrogram');
// Listen for file selection
fileInput.addEventListener('change', function() {
// Get the selected file
const file = fileInput.files[0];
// Create a FileReader object
const reader = new FileReader();
// Listen for the file to be loaded
reader.addEventListener('load', function() {
// Create an audio context
const context = new AudioContext();
// Create an audio buffer from the audio file
context.decodeAudioData(reader.result, function(buffer) {
// Create a spectrogram
const spectrogram = createSpectrogram(buffer, context);
// Draw the spectrogram on the canvas
drawSpectrogram(spectrogram, spectrogramCanvas);
});
});
// Read the file as an array buffer
reader.readAsArrayBuffer(file);
});
// Create a spectrogram from an audio buffer
function createSpectrogram(buffer, context) {
// Create an offline audio context
const offlineContext = new OfflineAudioContext(buffer.numberOfChannels, buffer.length, buffer.sampleRate);
// Create a source node from the audio buffer
const source = offlineContext.createBufferSource();
source.buffer = buffer;
// Create a analyser node
const analyser = offlineContext.createAnalyser();
// Connect the source and analyser nodes
source.connect(analyser);
analyser.connect(offlineContext.destination);
// Start the source node
source.start(0);
// Render the audio
return offlineContext.startRendering();
}
// Draw a spectrogram on a canvas
function drawSpectrogram(spectrogram, canvas) {
// Get the canvas context
const context = canvas.getContext('2d');
// Get the image data for the spectrogram
const imageData = context.createImageData(spectrogram.width, spectrogram.height);
// Draw the spectrogram on the image data
spectrogram.forEach((value, i) => {
const j = i * 4;
imageData.data[j] = value;
imageData.data[j + 1] = value;
imageData.data[j + 2] = value;
imageData.data[j + 3] = 255;
});
// Draw the image data on the canvas
context.putImageData(imageData, 0, 0);
}
</script>
</body>
</html>

Related

How do I accept file uploads from pasting a file into the browser?

Accepting image uploads from a paste into the browser window is much easier than traditional file upload form inputs and even the newer style drag 'n' drop file uploads.
How do I implement it?
Here's an example PHP/JavaScript page that accepts drag 'n' drop image uploads. It's not dependent on PHP though - you could adapt it quite easily to work with another server-based language. This code was based on a snippet I found on jsFiddle by someone called Nick.
This is a full page - so you should be able to copy the code below and put it in a file on your web-server as-is (if you're not running PHP then you'll need to update the PHP code at the top or point the form to your own form handler script).
<?php
if (!empty($_POST)) {
// Handle the POSTed data here - the image is actually base64 encoded data in
// the $_POST['myTextarea'] variable which you can run through the base64_decode()
// function and then write to a file
$pos = strpos($_POST['myTextarea'], 'base64,');
$encoded = substr($_POST['myTextarea'], $pos + 7);
$raw = base64_decode($encoded);
// Show the base64 encoded $data - use the $raw variable when writing to a file
var_dump($_POST);
exit;
}
?>
<!DOCTYPE html >
<html>
<body>
<h1>File upload using paste</h1>
<p>
You can paste an image, which is on your clipboard, into this window and it will appear below.
If you use Windows you can press <b>PrtScr</b> to get a screenshot on your clipboard. Then
press <b>CTRL+V</b> to paste it into this document.
</p>
<!-- PUT THE ADDRESS OF YOUR FORM HANDLER SCRIPT IN THE ACTION ATTRIBUTE -->
<form action="" method="post">
<div id="form-elements-container">
<input type="text" value="An example text input..." name="myTextInput"><br />
<input type="submit" value="Submit form"><br />
</div>
</form>
<!-- THIS IS WHERE THE IMAGE THUMBNAILS WILL APPEAR -->
<div id="images-container"></div>
<script>
counter = 0;
document.body.onpaste = function (e)
{
// use event.originalEvent.clipboard for newer chrome versions
var items = (e.clipboardData || e.originalEvent.clipboardData).items;
// Find pasted image among pasted items
var blob = null;
for (var i=0; i<items.length; i++) {
if (items[i].type.indexOf("image") === 0) {
blob = items[i].getAsFile();
}
}
// Load image if there is a pasted image
if (blob !== null) {
var reader = new FileReader();
reader.onload = function(e)
{
// Create a new image object from the pasted data
var img = new Image();
img.src = e.target.result;
img.width = 128;
img.height = 128;
img.style.margin = '5px';
// Append the file to the document
document.getElementById('images-container').appendChild(img);
// Add a new textarea to the form
var textarea = document.createElement('textarea');
textarea.name = 'myTextarea_' + counter++;
textarea.value = img.src;
textarea.style.width = '200px';
textarea.style.height = '200px';
document.getElementById('form-elements-container').appendChild(textarea);
};
reader.readAsDataURL(blob);
}
}
</script>
</body>
</html>

Get play position of audio buffer in web audio API

I am making a browser based DJ app, and need an indicator to show how much of an audio track(audio buffer) has played on a waveform. I have a waveform but cant get the current position.
The Web Audio API uses a common time frame to synchronise all running and scheduled sounds, which can be read from AudioContext.currentTime. You can store the timestamp of when you start your sound and measure the elapsed time while the sound is played. By this you get the position in seconds. If you need the frame index position of the AudioBuffer you can multiply the elapsed time by the sampleRate of the AudioBuffer.
<!DOCTYPE HTML>
<html>
<head>
<title>Time Elapsed Demo</title>
<script>
const audioCtx = new AudioContext()
async function startSample() {
const output = document.getElementById("output")
const response = await fetch('test.wav')
const sample = await response.arrayBuffer()
const audioBuffer = await audioCtx.decodeAudioData(sample)
const sampleRate = audioBuffer.sampleRate
const sourceNode = audioCtx.createBufferSource()
sourceNode.buffer = audioBuffer
sourceNode.connect(audioCtx.destination)
let ended = false
sourceNode.onended = () => { ended = true }
const startTime = audioCtx.currentTime + 0.01
sourceNode.start(startTime)
function showTimeElapsed() {
if (ended == false) {
elapsed = audioCtx.currentTime - startTime;
if (elapsed >= 0) {
output.innerHTML =
"Time elapsed: " + elapsed.toFixed(3) + ", " +
"Frame index: " + (sampleRate * elapsed).toFixed(0)
}
requestAnimationFrame(showTimeElapsed)
}
}
showTimeElapsed()
}
</script>
</head>
<body>
<button id="start" onclick="startSample()">Start Sample</button>
<p>
<div id="output"></div>
</body>
</html>

Update html element from Javascript in Web Component

I am developing a PWA app using web components and vaadin router, this is my flow
index.html (main entry to PWA app)
index.html has reference to local ./js/index.js and routes
index.js has references to web components for each path
Each individual web components loads tensorflow js and also references local js for model train, fit and evaluate.
I am able to load tensorflow JS and my local js script in my web component, my question is how do I update the result in web component. This is my glitch link where is it working with traditional html, js and css.
Below I am putting relevant pieces of code for reference. The sequence of the code
Defining template.innerHTML
In the template there are 2 sections with id "canvas" and "prediction"
I need to update them from "image-classifier-mlp-script.js" which is my custom JS, traditionally I would have used
const PREDICTION_ELEMENT = document.getElementById('prediction');
const CANVAS = document.getElementById('canvas');
but I cannot do it in web component, so my question
What is the best pattern to update my html element id from my custom javascript?
const template = document.createElement('template')
template.innerHTML = `
<body>
<link rel="stylesheet" href="./css/image-classifier-mlp.css" />
<h1>TensorFlow.js MNIST classifier</h1>
<p>See console for even more outputs.</p>
<section class="box">
<h2>Input Image</h2>
<p>Input image is a 28x28 pixel greyscale image from MNIST dataset - a real hand drawn digit!</p>
<canvas id="canvas" width="28" height="28"></canvas>
</section>
<section class="box">
<h2>Prediction</h2>
<p>Below you see what number the trained TensorFlow.js model has predicted from the input image.</p>
<p>Red is a wrong prediction, Green is a correct one.</p>
<p id="prediction">Training model. Please wait...</p>
</section>
`
let initCalled = false;
let customInitCalled = false;
function loadCustomScript() {
console.log(`Before : Value of customInitCalled ${customInitCalled}`)
if(!customInitCalled) {
const script = document.createElement('script');
script.type = 'module';
script.async = true;
script.onload = function () {
customInitCalled = true;
console.log(`After : Value of customInitCalled ${customInitCalled}`)
}
script.src = '/js/image-classifier-mlp-script.js'
document.head.appendChild(script);
}
}
function loadTf() {
console.log(`Before : Value of initCalled ${initCalled}`)
if (!initCalled) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.onload = function () {
initCalled = true;
console.log(`After : Value of initCalled ${initCalled}`)
}
script.src = '//cdn.jsdelivr.net/npm/#tensorflow/tfjs#3.11.0/dist/tf.min.js'
document.head.appendChild(script);
}
}
class ImageClassifierMultiLayerPerception extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ 'mode': 'open' });
this._shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
console.log('mlp')
loadTf()
loadCustomScript()
}
}
customElements.define('image-classifier-mlp', ImageClassifierMultiLayerPerception)

How to insert a button in TinyMCE Editor to insert a local file as RAW image at current position?

How can i add a custom/default button to the TinyMCE Editor, to be able to select a local image and insert it as RAW (BASE64) image at current position in editor?
I just tried this from this answer.
window.onload = function () {
document.getElementById("fileName").onchange = function () {
var reader = new FileReader();
reader.readAsDataURL(this.files[0]);
reader.onload = function () {
console.log(reader.result);
document.getElementById("img").src = reader.result;
};
reader.onerror = function (error) {
console.log('Error: ', error);
};
};
};
<input type="file" name="fileName" id="fileName" /><br />
<img src="https://dummyimage.com/250x75/000/fff.png&text=Select+an+Image" id="img" style="max-width: 250px; max-height: 75px;" />
Using the above URL (see console or inspect element and find the src) and with inserting an image at the current position, you can insert the image inside the document at the current position of caret.

Connecting a simple gain node to Web Audio API file buffers

I can't seem to understand why this isn't working. I have no errors.I have done this using oscillators and it works fine. FYI this is a continuation of this thread:
Using Web Audio API to assign two sounds to two Divs and play each independently via a click event with javascript
Code:
<div id="divElement"></div>
<style>
#divElement{
background-color: orange;
width:70px;
height:100px;
left:143px;
}
</style>
<script>
var context = new webkitAudioContext(),
savedBuffer;
var playAudioFile = function () {
var source = context.createBufferSource();
var gainNode = context.createGainNode();
source.buffer = savedBuffer;
// START OF PROBLEM AREA: Gain not working but file still plays with no errors.
source.connect(gainNode);
gainNode.connect(context.destination);
gainNode.gain = 0;
// END OF PROBLEM AREA
source.noteOn(0); // Play sound immediately
};
var request = new XMLHttpRequest();
request.open('get', 'audio/A.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function () {
context.decodeAudioData(request.response,
function(incomingBuffer) {
savedBuffer = incomingBuffer;
var divElement = document.getElementById("divElement");
divElement.addEventListener("click", playAudioFile , false);
}
);
};
request.send();
</script>
Try gainNode.gain.value = 0 instead. gainNode.gain is an AudioGain object, which has the attribute value.
http://www.w3.org/TR/webaudio/#AudioGainNode