Is it possible to specify a range for the volume from AudioWorkletProcessor? - javascript

I followed the W3C's Documentation to implement a "vumeter" (code) using JavaScript's Web Audio API specifically the AudioWorkletProcessor and AudioWorkletNode interfaces. I would like to know why is the RMS level is compared with the previous volume multiplied by a "smoothing factor" (sorry I just started learning this topic so it's all new for me):
// Calculate the RMS level and update the volume.
rms = Math.sqrt(sum / samples.length);
this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR);
Also what is the range that the volume's variable can take? Is it possible to know so that we can assume that the volume will be a value in a 0...100 range?
Process method code:
process (inputs, outputs, parameters) {
const input = inputs[0];
// Note that the input will be down-mixed to mono; however, if no inputs are
// connected then zero channels will be passed in.
if (input.length > 0) {
const samples = input[0];
let sum = 0;
let rms = 0;
// Calculated the squared-sum.
for (let i = 0; i < samples.length; ++i)
sum += samples[i] * samples[i];
// Calculate the RMS level and update the volume.
rms = Math.sqrt(sum / samples.length);
this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR);
// Update and sync the volume property with the main thread.
this._nextUpdateFrame -= samples.length;
if (this._nextUpdateFrame < 0) {
this._nextUpdateFrame += this.intervalInFrames;
this.port.postMessage({volume: this._volume});
}
}
// Keep on processing if the volume is above a threshold, so that
// disconnecting inputs does not immediately cause the meter to stop
// computing its smoothed value.
return this._volume >= MINIMUM_VALUE;
}

Related

Implementing OneRule algorithmn in javascript [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
OneR, short for "One Rule", is a simple yet accurate classification algorithm that generates one rule for each predictor in the data, then selects the rule with the smallest total error as its "one rule".
I tried to find code samples on GitHub, but found only one, developed with R language. How could I implement this algorithm in Javascript?
What I have tried?
I am trying to implement following this sample article:
https://www.saedsayad.com/oner.htm
class OneR {
/**
* Pass dataset which will be an array of values.
* Last value is classifcator's value.
* All other values are predictors.
*
* Example
*
* The meaning of sequence values:
* |Outlook|Temp|Humidity|Windy|Play Golf|
*
* Representation of a sequence:
* ['rainy', 'hot', 'high', 0, 0]
*
* True and False are represented as zeros or ones
*/
constructor(data = []) {
this.data = data;
this.frequences = {};
}
predict() {
if (this.data && this.data.length > 0) {
const firstRow = this.data[0];
const predictorCount = firstRow.length - 1;
let classifcator;
// For each predictor,
for (let i = 0; i < predictorCount; i++) {
// For each value of that predictor, make a rule as follos;
for (let y = 0; y < this.data.length; y++) {
// Count how often each value of target (class) appears
classifcator = this.data[y][predictorCount];
console.log(classifcator);
// Find the most frequent class
// Make the rule assign that class to this value of the predictor
}
// Calculate the total error of the rules of each predictor
}
// Choose the predictor with the smallest total error
} else {
console.log("Cannot predict!");
}
}
}
module.exports = {
OneR
};
I have loaded data from csv
rainy,hot,high,0,0
rainy,hot,high,1,0
overcast,hot,high,0,1
sunny,mild,high,0,1
sunny,cool,normal,0,1
sunny,cool,normal,1,0
overcast,cool,normal,1,1
rainy,mild,high,0,0
rainy,cool,normal,0,1
sunny,mild,normal,0,1
rainy,mild,normal,1,1
overcast,mild,high,1,1
overcast,hot,normal,0,1
sunny,mild,high,1,0
If I understand correctly how the frequency tables must be compared (lowest error rate, highest accuracy), you could use Maps so to cope with non-string types if ever necessary.
Although your example has target values that are booleans (0 or 1), in general they could be from a larger domain, like for example "call", "fold", "raise", "check".
Your template code creates a class, but I honestly do not see the benefit of that, since you can practically only do one action on it. Of course, if you have other actions in mind, other than one-rule prediction, then a class could make sense. Here I will just provide a function that takes the data, and returns the number of the selected predictor and the rule table that goes with it:
function oneR(data) {
if (!data && !data.length) return console.log("Cannot predict!");
const predictorCount = data[0].length - 1;
// get unique list of classes (target values):
let classes = [...new Set(data.map(row => row[predictorCount]))];
let bestAccuracy = -1;
let bestFreq, bestPredictor;
// For each predictor,
for (let i = 0; i < predictorCount; i++) {
// create frequency table for this predictor: Map of Map of counts
let freq = new Map(data.map(row => [row[i], new Map(classes.map(targetValue => [targetValue, 0]))]));
// For each value of that predictor, collect the frequencies
for (let row of data) {
// Count how often each value of target (class) appears
let targetValue = row[predictorCount];
let predictorValueFreq = freq.get(row[i]);
let count = predictorValueFreq.get(targetValue);
predictorValueFreq.set(targetValue, count+1);
}
// Find the most frequent class for each predictor value
let accuracy = 0;
for (let [predictorValue, predictorValueFreq] of freq) {
let maxCount = 0;
let chosenTargetValue;
for (let [targetValue, count] of predictorValueFreq) {
if (count > maxCount) {
// Make the rule assign that class to this value of the predictor
maxCount = count;
chosenTargetValue = targetValue;
}
}
freq.set(predictorValue, chosenTargetValue);
accuracy += maxCount;
}
// If this accuracy is best, then retain this frequency table
if (accuracy > bestAccuracy) {
bestAccuracy = accuracy;
bestPredictor = i;
bestFreq = freq;
}
}
// Return the best frequency table and the predictor for which it applies
return {
predictor: bestPredictor, // zero-based column number
rule: [...bestFreq.entries()]
}
}
let data = [
["rainy","hot","high",0,0],
["rainy","hot","high",1,0],
["overcast","hot","high",0,1],
["sunny","mild","high",0,1],
["sunny","cool","normal",0,1],
["sunny","cool","normal",1,0],
["overcast","cool","normal",1,1],
["rainy","mild","high",0,0],
["rainy","cool","normal",0,1],
["sunny","mild","normal",0,1],
["rainy","mild","normal",1,1],
["overcast","mild","high",1,1],
["overcast","hot","normal",0,1],
["sunny","mild","high",1,0]
];
let result = oneR(data);
console.log(result);

How to calculate DB SPL given DB FS

I'm working on creating a volume fader using web audio api that allows for a gain of +12db. So far I've calculated the dbFS value for the peak meter.
How do I derive a dbSPL value given the dbFS? From what I understand, volume faders in daw use dbSPL while peak meters use dbFS. Somehow a conversion must be taking place. How do I implement the conversion ?
what I have so far:
//create random amplitude values ranging 0 to 1
let amplitudes = Array.from({ length: 16 }, (_,i) => Math.random() * 1);
//calculate average amplitude
let average = amplitudes.reduce((a, b) => a + b) / 16;
// derive dbFS of given average amplitude value
let dbFS = 20 * Math.log10(average)
//update input element value
peakMeter.value = dbFS;
Now I would like to do the same for volume fader
let dbSPL = //?????
fader.value = spl
//Translate dbSPL back to dbFS then update gain value
audioNode.gain.value = // ???
Any feedback would be helpful.

A DFT analysis in a low speed data sampling

I have some sample data of vibrations analysis from sensors installed on electrical motors. The sampling is made once or, at most, 3 times per day. The values can be expressed in g, gE or mm/s.
I’m developing a personal algorithm in JavaScript to process some samples and perform a DFT. It’s a simple code that uses brute force to process my results. I compared the results (real and imaginary parts) from JavaScript and from MATLAB results and they matched perfectly.
However, my sampling rate is very slow. Because of this, I have a lot of questions which I couldn’t find the answers on my searches:
Is it possible to apply a DFT analysis on a slow sampling data as this?
How can I determine the correct frequency scale for the X axis? It’s complicated for me because I don’t have an explicit Fs (sampling rate) value.
In my case, would it be interesting to apply some window function like Hanning Window (suitable for vibrations analyses)?
JavaScriptCode:
//Signal is a pure one-dimensional of real data (vibration values)
const fft = (signal) => {
const pi2 = 6.2832 //pi const
let inputLength = signal.length;
let Xre = new Array(inputLength); //DFT real part
let Xim = new Array(inputLength); //DFT imaginary part
let P = new Array(inputLength); //Power of spectrum
let M = new Array(inputLength); //Magnitude of spectrum
let angle = 2 * Math.PI / inputLength;
//Hann Window
signal = signal.map((x, index) => {
return x * 0.5 * (1 - Math.cos((2 * Math.PI * index) / (inputLength - 1)));
});
for (let k = 0; k < inputLength; ++k) { // For each output element
Xre[k] = 0; Xim[k] = 0;
for (let n = 0; n < inputLength; ++n) { // For each input element
Xre[k] += signal[n] * Math.cos(angle * k * n);
Xim[k] -= signal[n] * Math.sin(angle * k * n);
}
P[k] = Math.pow(Xre[k], 2) + Math.pow(Xim[k], 2);
M[k] = Math.sqrt(Math.pow(Xre[k], 2) + Math.pow(Xim[k], 2));
}
return { Xre: Xre, Xim: Xim, P: P, M: M.slice(0, Math.round((inputLength / 2) + 1)) };
}
The first figure shows the charts results (time domain on the left side and frequency domain on the right side).
The second figure shows a little bit of my data samples:
Obs.: I'm sorry for the writing. I'm still a beginner English student.
The frequency doesn't matter. A frequency as low as 1/day is just as fine as any other frequency. But consider the Nyquist-Shannon theorem.
This is problematic. You need a fix sampling frequency for a DFT. You could do interpolation as preprocessing. But better would be to do the sampling at fix times.

Record audio, sync to loop, offset latency and export portion

I am building a web app which allows users to listen to a loop of instrumental music and then record vocals on top. This is all working using Recorder.js however there are a few problems:
There is latency with recording, so this needs to be set by the user before pressing record.
The exported loop is not always the same length as the sample rate might not match the time needed exactly
However since then I went back to the drawing board and asked: What's best for the user?. This gave me a new set of requirements:
Backing loop plays continuously in the background
Recording starts and stops whenever the user chooses
Recording then plays back in sync with loop (the dead time between loops is automatically filled with blank audio)
User can slide an offset slider to adjust for small timing issues with latency
User can select which portion of the recording to save (same length as original backing loop)
Here's a diagram of how that would look:
Logic I have so far:
// backing loop
a.startTime = 5
a.duration = 10
a.loop = true
// recording
b.startTime = 22.5
b.duration = 15
b.loop = false
// fill blank space + loop
fill = a.duration - (b.duration % a.duration) // 5
c = b.buffers + (fill * blankBuffers)
c.startTime = (context.currentTime - a.startTime) % a.duration
c.duration = 20
c.loop = true
// user corrects timing offset
c.startTime = ((context.currentTime - a.startTime) % a.duration) - offset
// user choose favourite loop
? this is where I start to lose the plot!
Here is an example of chopping the buffers sent from Recorder.js:
// shorten the length of buffers
start = context.sampleRate * 2; // start at 2 seconds
end = context.sampleRate * 3; // end at 3 seconds
buffers.push(buffers.subarray(start, end));
And more example code from the previous versions i've been working on:
https://github.com/mattdiamond/Recorderjs/issues/105
Any help in working out how to slice the buffers for the exported loop or improving this logic would be greatly appreciated!
UPDATE
Using this example I was able to find out how to insert blank space into the recording:
http://mdn.github.io/audio-buffer/
I've now managed to almost replicate the functionality I need, however the white noise seems off. Is there a miscalculation somewhere?
http://kmturley.github.io/Recorderjs/loop.html
I managed to solve this by writing the following logic
diff = track2.startTime - track1.startTime
before = Math.round((diff % track1.duration) * 44100)
after = Math.round((track1.duration - ((diff + track2.duration) % track1.duration)) * 44100)
newAudio = [before data] + [recording data] + [after data]
and in javascript code it looks like this:
var i = 0,
channel = 0,
channelTotal = 2,
num = 0,
vocalsRecording = this.createBuffer(vocalsBuffers, channelTotal),
diff = this.recorder.startTime - backingInstance.startTime + (offset / 1000),
before = Math.round((diff % backingInstance.buffer.duration) * this.context.sampleRate),
after = Math.round((backingInstance.buffer.duration - ((diff + vocalsRecording.duration) % backingInstance.buffer.duration)) * this.context.sampleRate),
audioBuffer = this.context.createBuffer(channelTotal, before + vocalsBuffers[0].length + after, this.context.sampleRate),
buffer = null;
// loop through the audio left, right channels
for (channel = 0; channel < channelTotal; channel += 1) {
buffer = audioBuffer.getChannelData(channel);
// fill the empty space before the recording
for (i = 0; i < before; i += 1) {
buffer[num] = 0;
num += 1;
}
// add the recording data
for (i = 0; i < vocalsBuffers[channel].length; i += 1) {
buffer[num] = vocalsBuffers[channel][i];
num += 1;
}
// fill the empty space at the end of the recording
for (i = 0; i < after; i += 1) {
buffer[num] = 0;
num += 1;
}
}
// now return the new audio which should be the exact same length
return audioBuffer;
You can view a full working example here:
http://kmturley.github.io/Recorderjs/loop.html

How to resort a Javascript array when only the first value has changed

I've got a little app that recalculates the apportionment of seats in Congress in each state as the user changes the population hypothetically by moving counties between states. There are functionally infinite combinations, so I need to compute this on the fly.
The method is fairly straightforward: You give each state 1 seat, then assign the remaining 385 iteratively by weighting them according to population / ((seats * (seats + 1)) and assigning the seat to the top priority state.
I've got this working fine the obvious way:
function apportion(states) {
var totalReps = 435;
// assign one seat to each state
states.forEach(function(state) {
state.totalReps = 1;
totalReps -= 1;
state.priority = state.data.population / Math.sqrt(2); //Calculate default quota
});
// sort function
var topPriority = function(a, b) {
return b.priority - a.priority;
};
// assign the remaining 385
for (totalReps; totalReps > 0; totalReps -= 1) {
states.sort(topPriority);
states[0].totalReps += 1;
// recalculate the priority for this state
states[0].priority = states[0].data.population / Math.sqrt(states[0].totalReps * (states[0].totalReps + 1));
}
return states;
}
However, it drags a little when called several times a second. I'm wondering whether there's a better way to place the state that received the seat back in the sorted array other than by resorting the whole array. I don't know a ton about the Javascript sort() function and whether it will already do this with maximal efficiency without being told that all but the first element in the array is already sorted. Is there a more efficient way that I can implement by hand?
jsFiddle here: http://jsfiddle.net/raphaeljs/zoyLb9g6/1/
Using a strategy of avoiding sorts, the following keeps an array of priorities that is aligned with the states object and uses Math.max to find the highest priority value, then indexOf to find its position in the array, then updates the states object and priorities array.
As with all performance optimisations, it has very different results in different browsers (see http://jsperf.com/calc-reps), but is at least no slower (Chrome) and up to 4 times faster (Firefox).
function apportion1(states) {
var totalReps = 435;
var sqrt2 = Math.sqrt(2);
var priorities = [];
var max, idx, state, n;
// assign one seat to each state
states.forEach(function(state) {
state.totalReps = 1;
state.priority = state.data.population / sqrt2; //Calculate default quota
priorities.push(state.priority);
});
totalReps -= states.length;
while (totalReps--) {
max = Math.max.apply(Math, priorities);
idx = priorities.indexOf(max);
state = states[idx];
n = ++state.totalReps;
state.priority = state.data.population / Math.sqrt(n * ++n);
priorities[idx] = state.priority;
}
return states;
}
For testing I used an assumed states object with only 5 states, but real population data. Hopefully, with the full 50 states the benefit will be larger.
Another strategy is to sort on population since that's how the priorities are distributed, assign at least one rep to each state and calculate the priority, then run from 0 adding reps and recalculating priorities. There will be a threshold below which a state should not get any more reps.
Over to you. ;-)
Edit
Here's a really simple method that apportions based on population. If may allocation one too many or one too few. In the first case, find the state with the lowest priority and at least 2 reps (and recalc priority if you want) and take a rep away. In the second, find the state with the highest priority and add one rep (and recalc priority if required).
function simple(states) {
var totalPop = 0;
var totalReps = 435
states.forEach(function(state){totalPop += state.data.population});
var popperrep = totalPop/totalReps;
states.forEach(function(state){
state.totalReps = Math.round(state.data.population / popperrep);
state.priority = state.data.population / Math.sqrt(state.totalReps * (state.totalReps + 1));
});
return states;
}
Untested, but I'll bet it's very much faster than the others. ;-)
I've updated the test example for the simple function to adjust if the distribution results in an incorrect total number of reps. Tested across a variety of scenarios, it gives identical results to the original code even though it uses a very different algorithm. It's several hundred times faster than the original with the full 50 states.
Here's the final version of the simple function:
function simple(states) {
var count = 0;
var state, diff;
var totalPop = states.reduce(function(prev, curr){return prev + curr.data.population},0);
var totalReps = 435
var popperrep = totalPop/totalReps;
states.forEach(function(state){
state.totalReps = Math.round(state.data.population / popperrep) || 1;
state.priority = state.data.population / Math.sqrt(state.totalReps * (state.totalReps + 1));
count += state.totalReps;
});
// If too many reps distributed, trim from lowest priority with 2 or more
// If not enough reps distributed, add to highest priority
while ((diff = count - totalReps)) {
state = states[getPriority(diff < 0)];
state.totalReps += diff > 0? -1 : 1;
count += diff > 0? -1 : 1;
state.priority = state.data.population / Math.sqrt(state.totalReps * (state.totalReps + 1));
// console.log('Adjusted ' + state.data.name + ' ' + diff);
}
return states;
// Get lowest priority state with 2 or more reps,
// or highest priority state if high is true
function getPriority(high) {
var idx, p = high? 0 : +Infinity;
states.forEach(function(state, i){
if (( high && state.priority > p) || (!high && state.totalReps > 1 && state.priority < p)) {
p = state.priority;
idx = i;
}
});
return idx;
}
}

Categories

Resources