freeDSP / freeDSP-aurora

freeDSP ADAU1452 with 8 analog input, 8 analog outputs, S/P-DIF I/O, ADAT I/O, USB Audio Class2, WiFi, Bluetooth
Creative Commons Attribution Share Alike 4.0 International
174 stars 54 forks source link

User friendly web interfaces for plugins. #122

Open exislow opened 1 year ago

exislow commented 1 year ago

The current web interfaces of the plugins are very basic and hard to handle. If you like to change, e.g. EQ settings you only have a simple input based interface. Why not make it interactive using some more javascript?

I would like to help and re-create the plugin webinterfaces in a more user friendly manner. There are already approaches to get output of several JS based frameworks on ESP32, for instance, https://github.com/ayushsharma82/ESP-DASH.

Is this something considerable? If yes, I would like to submit some PoCs.

exislow commented 1 year ago

@dspverden any comment is appreciated.

dspverden commented 1 year ago

I highly appreciate that. Although, I don't have the time to dive into the details of those frameworks atm. If you want to do something, just do it. The communication with the Aurora board is a simple REST API, data is transferred by JSON. It is easy to understand from the JavaScript I think. But be aware, that memory space on the ESP is very limited. That is the reason, why I did not use a ready-to-run framework. Most of them are too big. And depending on DSP plugin you need some space for user data (e.g. 4FIRs plugin with 4 presets each with four 4096 taps Its for the FIR filters).

archi commented 1 year ago

Re memory constraints: I am not a huge fan of cloud stuff, but maybe exposing just an API on the ESP (and maybe the basic UI?) and storing the static HTML/JS of the advanced UI somewhere else might be an option (at least for prototyping). E.g. I have a Linux NAS that's always-on, others have their RPi, etc. And if its just static code, it could even be stored on the users local filesystem. The ESP just needs setting the apropriate CORS headers to allow external access.

For the general public, you could also put it on a GitHub-hosted github.io (and maybe offer to install it as a PWA so it works offline; do PWAs work offline?).

IIRC you (Raphael) are already using a filesystem lib, I suppose that's already compressing the content? Is the mentioned userdata also handled by it? If the FIR floats are stored as ASCII there could also be potential savings by storing them in a binary format.

exislow commented 1 year ago

Memory limitation is the biggest problem here indeed. There are already some approaches getting popular JS frameworks running on a ESP32. Of course it will impossible to load your JS app with a lot of external packages and expect it to fit into the ESP memory. But a PoC is described here: https://www.pschatzmann.ch/home/2020/04/08/progressive-javascript-frameworks-with-microcontrollers-vue-js-on-esp32/

I will try to to port the 8channel plugin as an PoC and see how it goes. I will keep you updated.

@archi: This is a possible solution but creates even more complexity and dependencies. Not everybody has an NAS at home and also not everybody wants to expose its Aurora to the internet. Especially based on the experience with the majority of the users, who are using the Aurora it will be too confusing for them. I would prefer a standalone solution with all data providing by the local ESP.

dspverden commented 1 year ago

Cloud solutions are not good, indeed. Many users run their Aurora offline via an ad-hoc connection. They don't have access to a cloud then. In the beginning of Aurora I made a stand-alone app that runs locally on your machine. It communicated with Aurora via a REST API. The API API is somehow still the same. Yet, maintaining the native app was eating resources. Especially the Windows version was like hell. Simply too much for a one man show.

Regarding file system: All user settings (filter coeffs, eq settings etc.) are stored in a binary format on the internal NOR flash. The develop branch has already a commit that introduces a new feature: All html, js and css stuff can now be stored compress (.gz file) on the internal NOR flash.

archi commented 1 year ago

Exposing wouldn't be necessary, and a bad idea overall; I totally agree with you there :)

But there are at least these two options I can quickly come up with:

  1. Open the ESP's website on your LAN, as usual. But now there is just some minimal bootstrapper code, which loads the external resources (JS/HTML) from aurora-controller.github.io (could later be cached in the HTTP5 localStorage for offline use). For dev-work you'd need to point it to another source.
  2. Go to aurora-controller.github.io, enter the IP of your aurora (stored in a cookie) and then the Webapp on there just calls to the REST API (since JS runs locally in the browser, there is actually no access from an internet-based host to the Aurora happening).

Of course putting everything on the Aurora is better, and if you're looking for someone to bash "cloud-native" and ask questions later: I'm your man ;) It was just an idea in case the better/proper solution should turn out to be absolutely, definitely and under any and all circumstances not realistically feasible.

//edit: Ah, dspverden was a bit quicker than I was. With gzip added, this might already help a lot. You might also want to look at other algorithms supported by browsers these days. Usually the ESP should not need to decompress the HTML/JS, only deliver it with the apropriate headers.

dspverden commented 1 year ago

The ESP only delivers. There is a new python script available that compresses all the GUI stuff needed for a DSP plugin. I just used gzip because it is usually available on *nix machines and than I found a python lib for it.

exislow commented 1 year ago

I have finally found some time to redo the frontend. Here is a sneak preview of a very early alpha stage. I am happy about comments. The huge benefit will be the visualization of settings using line charts AND a feature to be able to import REW PEQ files.

grafik

Btw... How much space do we have left on the ESP32?

Also while refaactoring the frontend I have noticed that the backend API is not built according to best practices. But this is a whole another task and not for now...

MarsianCRaute commented 1 year ago

I like the style of the current GUI. Dark, minimalistic and not too many buttons to click. Line charts would be great though. REW filter import.... take my money!

exislow commented 1 year ago

Progress is going well... Here is another sneak preview -- but animated this time! E.g. in my oponion a bypass sbould be activated with only one click, so I put it on the main page. I have so many ideas for backend refactoring but first things first!

Untitled

Two big steps still to do for the frontend:

  1. Add all the AJAX stuff.
  2. Implement the Charts for all filter blocks.

And here I am kinda stuck. I would like to calculate and display the charts adequate based on the chosen filter and frequency. Every Filter (Bessel, Butterworth, Linkwitz-Riley) has its own curve. How do I calculate them for visualization. Does anybody has the formulas?

@dspverden: Maybe you can help here?

If somebody is interested in the technical facts: The frontend is build as a singlepage application (SPA) with the help of SvelteKit, Bulma and Chart.JS. This means, I can automate a lot of code generation and do not have the need to copy & paste, for instance, all the sound processing blocks, input selects etc. The temple engine handles this for me. I will push the code soon to my github account. Also due tree shaking and post processing the final web files are only a few hundred kbs in size. Still do not know, how much space we actually have left on the ESP32.

Zadagu commented 1 year ago

And here I am kinda stuck. I would like to calculate and display the charts adequate based on the chosen filter and frequency. Every Filter (Bessel, Butterworth, Linkwitz-Riley) has its own curve. How do I calculate them for visualization. Does anybody has the formulas?

I can explain to you, how to calculate frequency responses. First, you need to research the transfer function H(s) or H(z). H(s) is a transfer function in the Laplace/analog domain. H(z) is a transfer function in the Biliniear/digital domain. I think it is not that important which one you choose. I would use the H(s) because they are easier to find online and have no cramping near nyquist.

To get the amplitude for a frequency f, you need to calculate:

s = 1j * f * 2 * pi
a = abs(H(s))  # abs will remove the phase information and give us the amplitude response
y = 20 * log10(a)  # converting the amplitude to dB

where j is the imaginary unit.

To get the response of multiple filters, simply multiply their transfer functions. eg:

a = abs(H_lowpass(s) * H_highpass(s))

Update: Sometime the formulas you find on wikipedia don't contain the cutoff frequency. In that case, you have to substitute s by s / w_c, where w_c is the angular frequency of the cutoff point. For an example have a look into THE ART OF VA FILTER DESIGN by Vadim Zavalishin. On page 15 "Cutoff parameterization" there is an example how to do it. The book is a great read. I can recommend it to everyone who is interested in filter design and digital audio processing. https://www.native-instruments.com/fileadmin/ni_media/downloads/pdf/VAFilterDesign_2.1.0.pdf

dspverden commented 1 year ago

Hi,

Looks cool, what you did so far!

Regarding filter transfer functions: The formulas for each filter are given in the help file of SigmaStudio. There you can see how the parameters are used by the formulas.

Regarding memory: You have almost nothing. You have 1.5MB for user data. That includes everything: Stored preset and plugin files and GUI files. The file size for storing presets is a bit difficult to estimate. It depends on the plugin itself (which and how many filter blocks are used). I think the 4FIRs is a good guess how big it can be. You have one file per preset. My plugins define four presets. But if customers make their own forks, there could be more or less presets.

exislow commented 1 year ago

Thank both of you for the explanation of the transfer functions. I think, I have understood it so far (maybe there is still some potential left). SigmaStudio wiki has truly some transfer functions explained: https://wiki.analog.com/resources/tools-software/sigmastudio/toolbox/filters/general2ndorder

What are the coefficients for underneath the transfer functions? Is there a more efficient way to solve the transfer function using the coefficients?

Also the SigmaStudio wiki does not contain any transfer function details for Linkwitz-Riley filters. Any idea, where to get them? They are somehow explained here but a little bit tricky to understand: https://www.linkwitzlab.com/filters.htm

I have googled for some already existing transfer function implementations and came across "Bode Plots", which is basically the plotting of magnitude (amplitude) and phase. There are not much implementation for JS:

  1. https://observablehq.com/@mvelasco/bode-plot-of-a-transfer-function
  2. https://homes.esat.kuleuven.be/~maapc/Sofia/javascript/demo5/demo_transfer_function_bode.html
  3. https://github.com/asparagii/bodeplot-viza (https://asparagii.github.io/bodeplot-viz/ -> enter: 1 / s + 1 for a plot.)
  4. https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/blob/master/src/routes/ModalChartGeneric.svelte (my implementation based on this https://homes.esat.kuleuven.be/~maapc/Sofia/javascript/demo5/demo_transfer_function_bode.html)

I have a few question:

  1. My implementation of 1. gives me totally different results (see screenshots below) for a Bessel filter n = 3, but I have copied more or less the code. What is going?

Screenshot 2022-11-04 um 21 39 17 Screenshot 2022-11-04 um 21 39 34

  1. If I implement the Bessel filter myself (according to the information taken from https://github.com/freeDSP/freeDSP-aurora/issues/122#issuecomment-1303432499), I will get these results (total non-sense):

Screenshot 2022-11-04 um 21 42 28

The implementation is here in the currently commented section: https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/blob/master/src/routes/ModalChartGeneric.svelte#L105

Somehow I am stuck again, maybe due to the fact, that I have just seen mathematical equation today and haven't sorted everything out so far...

I appreciate any comments on this!

Alternatively... What do you think? Is any implementation of the Bode Plots (1. - 3.) correct and could be altered successfully for our purposes? What do you think looks the most promising?

Zadagu commented 1 year ago

https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/blob/master/src/routes/ModalChartGeneric.svelte#L106 replace the 20 by mathjs.complex(0, 1). But I haven't tested it.

dspverden commented 1 year ago

Have a look at the file AudioFilterFactory.cpp in the ESP32 code. There you will see how the coefficients for each filter that is used by aurora are calculated.

exislow commented 1 year ago

https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/blob/master/src/routes/ModalChartGeneric.svelte#L106 replace the 20 by mathjs.complex(0, 1). But I haven't tested it.

Thank you for the hint. I had totally forgotten the complex number. So I did basically a small re-implementation (https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/blob/1756f4c9346f487d820b5cf377c6b535d96f9d52/src/routes/ModalChartGeneric.svelte#L106-L108) based on https://github.com/asparagii/bodeplot-viz/blob/master/src/script.js#L82

The Bessel 3rd order filter looks now like this (axis are adjusted):

grafik

Can anybody tell me, if this output is correct for a 3rd order Bessel filter? If it is correct I would like to re-facotr and optimize my code based on this. Right now I am still a little bit confused and not just, if this output is correct.

If this output is correct my next question would be:

  1. How to I shift this curve adequately to the desired low pass frequency, e.g. 200 Hz? Right now this filter starts at 0 but it should start depending on the desired cut off frequency.

If I follow this suggestion:

Update: Sometime the formulas you find on wikipedia don't contain the cutoff frequency. In that case, you have to substitute s by s / w_c, where w_c is the angular frequency of the cutoff point.

and w_c = 2 * PI * 200 = 1256.63 which results in an 3rd order Bessel filter const transfer_function = mathjs.parse('15 / ((s^3/1256.63) + (6 * (s^2/1256.63)) + (15 * (s/1256.63)) + 15)') the output of the cart is like this:

grafik

As you see, the filter starts do cut-off around 22Hz and not at 200Hz. What did I do wrong?

Or must be the formula like this const transfer_function = mathjs.parse('15 / ((s/1256.63)^3 + (6 * (s/1256.63)^2) + (15 * (s/1256.63)) + 15)')? But then the cart looks like this, which seems to be wrong as well:

grafik

Have a look at the file AudioFilterFactory.cpp in the ESP32 code. There you will see how the coefficients for each filter that is used by aurora are calculated.

Thank you very much. I should have found that by myself...

Zadagu commented 1 year ago

Or must be the formula like this const transfer_function = mathjs.parse('15 / ((s/1256.63)^3 + (6 (s/1256.63)^2) + (15 (s/1256.63)) + 15)')?

That's the correct way doing it.

https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/blob/1756f4c9346f487d820b5cf377c6b535d96f9d52/src/routes/ModalChartGeneric.svelte#L106-L108

I don't fully understand the plotting code. But to me it looks like you forgot 2pi after your refactoring. const magnitude = compiled_tf.eval({s: mathjs.complex(0, f)}).toPolar().r it should be: `const magnitude = compiled_tf.eval({s: mathjs.complex(0, f 2 * mathjs.pi)}).toPolar().r Btw you dont needabs()andtoPolar().r` because they should be mathematically the same.

exislow commented 1 year ago

Damn you are genius! Now it looks somehow as expected:

grafik

Just be sure: Do you think, based on the output, that the implementation of the 3rd order Bessel filter is correct? If yes, I will re-factor and optimize my code, so I can implement it for each block respectively. Right now the line chart code + calculation is just a prototype.

I don't fully understand the plotting code.

// Math.JS is basically able to convert every formula given as string to a computable equation. This is what is done in the next two lines.
const transfer_function = mathjs.parse('15 / ((s/1256.63)^3 + (6 * (s/1256.63)^2) + (15 * (s/1256.63)) + 15)')
const compiled_tf = transfer_function.compile()

// Here I basically tell MathJS, that s is my variable and should be replaced with `mathjs.complex(0, f * 2 * Math.PI)`
// This is basically the aquivalent of `H(s)`.
const magnitudeEquation = compiled_tf.eval({s: mathjs.complex(0, f * 2 * Math.PI)})

// Since MathJS has computed `H(s)` I ask MathJS to convert the result to polar coordinates and return me the real number.
const magnitudePolar = magnitudeEquation.toPolar()
const magnitude = magnitudePolar.r

Is it more understandable now?

Update:

Have a look at the file AudioFilterFactory.cpp in the ESP32 code.

Here is the source: https://github.com/freeDSP/freeDSP-aurora/blob/6ebdf607493865ae2f485185a327978fcdd1b969/SOURCES/WEBAPP/ESP32/aurora/AudioFilterFactory.cpp

The filters are implemented as BiQuads, which means it uses H(z). I think, I will also implement the line calculation with BiQuads for the final version to be consistent. Does anybody can tell me the definition of z similar to s as described with s = 1j * f * 2 * PI? Thanks!

Zadagu commented 1 year ago

Do you think, based on the output, that the implementation of the 3rd order Bessel filter is correct?

The shape looks right, but the slope is too steep. Any 3rd order lowpass should be -18dB/octave. And your plot is showing -27dB per octave.

Is it more understandable now?

Sorry, I meant the code around was not clear to me. That part was already good. But I think this is mostly me, not knowing the plotting framework.

Does anybody can tell me the definition of z similar to s as described with s = 1j f 2 * PI? Thanks!

The z-space is defined on the unit circle. Where f=0 is z=1 and f = nyquist is z = -1. z = e ^ (2 * pi * 1j * f / f_s) where f_s is the sampling frequency. FYI: https://en.wikipedia.org/wiki/Bilinear_transform

exislow commented 1 year ago

The shape looks right, but the slope is too steep. Any 3rd order lowpass should be -18dB/octave. And your plot is showing -27dB per octave.

Under this assumption the output of my formula should actually be (if cut-off frequency is 200 Hz):

but this is not the case. My plot show at

What is wrong...? I have fixed the x-axis scaling for better readability:

grafik

Furthermore thank you for the z-space function and the Wikipedia link. I am getting more and more in detail with this topic. Feels great!

Zadagu commented 1 year ago

The newest one looks right. :+1: 1kHz: -20dB 2kHz: -38dB

exislow commented 1 year ago

I do not get... Why is this correct? I thought a 3rd order lowpass has 18dB/octave. My cut-off frequency was is set to 200 Hz. Thus the result should be:

Where is my calculation wrong? Or can you explain me how you calculated this values?

1kHz: -20dB 2kHz: -38dB

Zadagu commented 1 year ago

Those filters doesn't reach their maximum steepness at the cutoff frequency. So you need to look at a point where the gradient doesn't change anymore.

Where is my calculation wrong? Or can you explain me how you calculated this values?

1kHz: -20dB 2kHz: -38dB

I took those numbers out of your last diagram.

exislow commented 1 year ago

Where is my calculation wrong? Or can you explain me how you calculated this values?

1kHz: -20dB 2kHz: -38dB

I took those numbers out of your last diagram.

I know that. I was more like: Can you explain me, why those numbers are correct, despite it is in my diagram. But anyway, I take like it is for now and try to implement the remaining features. Those algorithms can be changed easily later, if any mistake is still there.

Thank you very much (honestly!) for your support so far. I appreciate it a lot.

Zadagu commented 1 year ago

I know that. I was more like: Can you explain me, why those numbers are correct, despite it is in my diagram.

Have a look into the art of VA filter design. Chapter 2.9 "Poles and Zeros" section "Rolloff". https://www.native-instruments.com/fileadmin/ni_media/downloads/pdf/VAFilterDesign_2.1.0.pdf It's a well written book and understandable even for me as a software developer without a degree in electrical engineering.

exislow commented 1 year ago

Just to give you a small update: I'm still on it. Almost finished to re-implement the current functions in a more modern way. Soon all the advantages to ease up your life will be implemented, so I can release the first version of the new 8channel plugin.

fabguitar69690 commented 1 year ago

Hi ! existlow do you continue to develop the new interface ?

exislow commented 1 year ago

Despite the fact that I was busy with something else in the meantime I am still developing this UI. You can see the progress in my personal repo (link posted somewhere up here). There is "only" the PEQ visualization part left to be done. I hope, I can release the UI for beta testing soon.

exislow commented 9 months ago

Just a quick update here: I am currently working on a first usable beta of this plugin. But there are some issues to compile all the Svelte code into a single JS file. After this problem is solved I will offer the plugin for download for everybody who likes to test it.

The current code can be found here: https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/tree/biquad

P.S.: I know, it's been a while, but this had some private reasons...

exislow commented 9 months ago

Finally, I am able to present the first running beta of my work:

preview

This is the new web interface for the 8channel v2.2.2 plugin. You can get the beta here: 8channels_new_v2.2.2.zip As usual: Please backup your presets before you upload this plugin. Afterwards you need to upload your presets again. I recommend NOT to use Internet Explorer!

What are the advantages of this new web interface?

Known Issues:

If you like to submit bugs, ideas etc. please create an issue here: https://github.com/exislow/freeDSP-Aurora-Svelte-Frontend/issues

I will not support any issues in this thread. Thank you for your understanding.