Using Javascript to Create Interactive Visualizations of Fourier Transform

Utilizing this particular library for performing a Fast Fourier Transform on an audio file, my next step involves visualizing the output using canvasjs. Unfortunately, I am unsure about how to proceed with this visualization.

I find myself puzzled regarding the choice of values for the x and y axes. Should they represent frequency and amplitude? If so, what is the correct approach for setting these values? Also, should the maximum value for the x axis equal the highest frequency recorded? And if that's the case, what would be the appropriate step value? (I have already calculated the magnitude and the maximum frequency).

Your assistance in this matter would be greatly appreciated.

Edit: My attempt to replicate this resulted in a subpar outcome. While the magnitude seems decent, the phase appears to be off. Suspecting that the issue might lie with the Math.atan2() function, which calculates from two numbers, I experimented with Math.js and arrays but ended up with the same result. (The desired result can be seen in the provided link).

    for (var i = 0; i < 10 - 1/50; i+=1/50) {
      realArray.push(Math.sin(2 * Math.PI * 15 * i) + Math.sin(2 * Math.PI * 20 * i));
    }

    //Phase
    counter = 0;
    for (var i = 0; i < realArray.length ; i++) {
      rnd.push({x: i, y: (Math.atan2(imag[counter], realArray[counter]) * (180 / Math.PI))});
      counter++;
    }

    //Magnitude
    counter = 0 ;
    for (var i = 0; i < realArray.length  ; i++) {          
      rnd1.push({x: i , y: Math.abs(realArray[counter])});
      counter++;
    }

This situation has left me feeling completely lost. Any advice you could offer would be greatly appreciated.

https://i.sstatic.net/48tIA.png

Answer №1

Executing the given code from a server (localhost is acceptable) helps to avoid encountering cross-origin issues that can arise when trying to serve from a file:/// url.

I have studied the specifications for webkit audio and re-implemented getByteFreqData in javascript. This implementation allows for processing an audio file without relying on the (potentially flawed) AudioWorkers implementation (although this may have been rectified by now, I haven't verified it recently)

In general, time is depicted along the X-axis, frequency along the Y-axis, and the intensity of frequencies in any particular bin is represented by the pixel's intensity – you are free to choose any palette you prefer. I'm not quite sure where I drew inspiration for the one used - perhaps it was from Audacity's code, or maybe from some webkit Audio demo I encountered long ago.

Here are two images displaying the output (spectrum scaled to 50%):

https://i.sstatic.net/YgxHX.png https://i.sstatic.net/ONnYs.png

An important point to note is that a 5-minute recording doesn't need to be played in real-time to achieve a sample-accurate display. On the other hand, using the webkit audio method requires either (a) as much time as the sound-file duration or (b) might result in broken output due to dropped frames while employing AudioWorkers (tested on Chrome Version 57.20.2987.98 x64).

This implementation took me days/weeks, so please forgive any messy/redundant code!

1. fft.js

(JavaScript code removed for brevity)

2. offlineAudioContext.html

(JavaScript code removed for brevity)

Answer №2

Displayed below is an adaptation of the visualizations featured on the Matlab page referenced in the original query.

In a previous comment, I reconstructed some elements of the graph drawing code from the Spectrum analyzer. While I didn't address y-axis labels or output scaling, my focus was on the quality and accuracy of the visualizations. The data used to generate these visualizations faithfully reflects that calculated by Matlab and Octave, with particular attention paid to normalizing the data for the 2nd and 3rd graphs. The code was initially developed as a tool for visualizing data during the convolution of two audio signals using FFT for efficiency. (DFT code is included here for conciseness)

It's worth noting that utilizing floating point addition to compute the current time when generating samples can lead to accumulating errors over time. This explains why you had to adjust the loop incrementation in the code snippet provided. To avoid this issue, multiplying the step number by the interval between each step - as demonstrated in `fillSampleBuffer` - prevents the accumulation of floating point errors. Comparing the `currentTime` at each iteration highlights the difference. ;)

var complex_t = function(real, imag)
{
this.real = real;
this.imag = imag;
return this;
}

// Discrete Fourier Transform
// much slower than an FFT, but also considerably shorter
// and less complex (no pun intended!) - result the same
// returns an array of complex values
function dft( complexArray )
{
var nSamples = complexArray.length;
var result = [];

for (var outIndex=0; outIndex<nSamples; outIndex++)
{
var sumReal=0, sumImag=0;
for (var inIndex=0; inIndex<nSamples; inIndex++)
{
var angle = 2 * Math.PI * inIndex * outIndex / nSamples;
var cosA = Math.cos(angle);
var sinA = Math.sin(angle);
sumReal += complexArray[inIndex].real*cosA + complexArray[inIndex].imag*sinA;
sumImag += -complexArray[inIndex].real*sinA + complexArray[inIndex].imag*cosA;
}
result.push( new complex_t(sumReal, sumImag) );
}
return result;
}


function graphFormatData_t()
{
this.margins = {left:0,top:0,right:0,bottom:0};
this.graphTitle = '';
this.xAxisLabel = '';
this.yAxisLabel = '';
this.windowWidth = ''; //0.0107;
this.xAxisFirstTickLabel = '';
this.xAxisLastTickLabel = '';
return this;
}

// Rest of the code snippets remain unchanged...

canvas
{
border: solid 1px red;
}
<canvas id='sampleVis' width=430 height=340></canvas><br>
<canvas id='magnitudeVis' width=430 height=140></canvas><br>
<canvas id='phaseVis' width=430 height=140></canvas>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The order of event handlers in jQuery

I am currently setting up event binding for a text element dynamically. Take a look at the following code snippet: <input type="text" id="myTxt" /> <script type="text/javascript"> function attachEvent1(element){ element.keyup(func ...

How can the ID of a table row be retrieved if it is selected?

In my datatable, I have a list of Cars where each row contains a Car ID. A checkbox column has been added to the first cell in the table - when checked, the row is highlighted to show the user their selection. The goal is to retrieve the IDs of all selecte ...

What is the best way to generate a new component by triggering an event with Vue.js?

Imagine you have a list of posts retrieved from an ajax response, and now you want to provide users with the option to edit any specific post by clicking a button. One approach could be using the v-show directive to attach a form component to each post. Wh ...

Bootstrap: Navbar links do not receive active class in the scroll on time

I am utilizing Bootstrap 4. After implementing a smooth scroll feature, I had to adjust it by 80px due to my fixed navbar at the top. $('#navbar').find('a').click(function (event) { event.preventDefault(); var $anchor ...

TS18047 jest error: "object may be null"

I'm currently working on a test (jtest) for an angular component, but it keeps failing due to this particular error. Any thoughts on how to resolve this? :) it("should require valid email", () => { spectator.component.email.setValue( ...

AJAX submission of tinyMCE content - Encoding of special characters within table elements

Currently, I am integrating the tinyMCE editor plugin into my website. When I utilize a basic HTML page post to send the content to the backend PHP for saving it into a database or file, everything works fine. However, when I attempt to achieve the same ...

Angular is unable to POST to Rails server with the content-type set as application/json

Currently, I am attempting to send a POST request with Content-Type: application/json from Angular to my Rails backend. However, an error is being displayed in the console: angular.js:12578 OPTIONS http://localhost:3000/api/student_create 404 (Not Found ...

Merge corresponding elements from two arrays based on their indices

If I have two arrays of objects structured like this: var arr1 = [{name: 'Jay'}, {name: 'Bob'}]; var arr2 = [{age: 22}, {age: 30}]; I am looking to merge them into a combined array as follows: var arr3 = [{name: 'jay', age: ...

Calculating the total of selected values in Checkboxes and Selectors using KnockoutJS

I have already created this using jQuery. You can view it on JSFiddle: JSFiddle HTML: <div class="container"> <header> <h3>The Crazy Things We'll Do for Money</h3> <div class="small"><em>An ele ...

Designing a carousel-style menu list with navigation buttons for moving forward and backward

I'm running into some trouble while attempting to create a carousel. Firstly, the issue I am facing is that when you continuously click on the Next button, the function keeps working even after reaching the last item. I'm not sure how to make th ...

ng-disabled with function that activates on change

I am attempting to create a submit validation button that will only enable when all fields have been entered correctly. Due to the complexity of the validation process, I am unable to rely solely on formName.$invalid and need to write a custom function for ...

Upcoming construction: Issue encountered - The Babel loader in Next.js is unable to process .mjs or .cjs configuration files

Within my package.json file, I have set "type": "module" and "next": "^12.2.5". In my tsconfig.json: { "compilerOptions": { "target": "ES2022", "module": "esnext ...

Combining various Google calendar feeds into a single JSON object using JavaScript

I'm currently in the process of integrating JSON feeds from multiple Google calendars to organize upcoming events and showcase the next X number of events in an "Upcoming Events" list. While I initially achieved this using Yahoo! Pipes, I aim to elim ...

Issue with displaying value after rendering

After fetching pool data from the constants file, my task was to create a featured vault - the one with the highest APY Reward value obtained from the API. Even though I successfully fetched all three values and performed the calculations, I am facing an i ...

Does the useState hook have any connection to hoisting in React?

I am relatively new to React.js and JavaScript, currently working on a project where I need the ability to manually update my components as needed, due to limitations with a third-party library. After doing some research, I came across a pattern on the of ...

Distinguishing between a regular JavaScript variable and one annotated with a dollar sign

Many responses have addressed the question of using a dollar sign in JavaScript variables. In essence, the dollar sign functions as an identifier in JavaScript variables. However, I am curious if there are other distinctions between regular variables and ...

Angular encountering a null value within a pre-existing nested object

Upon receiving a fully populated object from my server to my angular service, everything appears correct in Postman and when I use JSON.stringify in the Angular component. However, I encounter an issue when trying to access nested objects within the view. ...

Completely digital Mongoose schema that resides solely in memory and is not saved permanently

Having trouble locating any documentation or references on this topic, which could suggest that I am approaching it incorrectly. Is there a way to utilize a Mongoose schema that is completely virtual, meaning it is not stored in the database? I have vari ...

I am curious about how this code links the Send button to the function. Additionally, I would like to know the process for creating

Having some trouble with my SPFx study project. Specifically, I'm confused about how the Send button is connected to the setComment() function in this context. I've also added an Update button that I want to use for updating the item created earl ...

The issue of double submission persists, with the prevention of the default action

I'm in need of assistance. Within my form, I have two buttons displayed on the page: "Save" and "Publish". Below is the corresponding HTML code: <button type="submit" class="button">Save</button> <button type="button" class="button" n ...