Parse textarea and display average via JavaScript - javascript

I've been searching and haven't found any way to do this in JavaScript or if there is a better way.
My form has a text area field that specific string is entered such as:
1/5/8 18 31.2 0 1847550953 13013135 5598945 3.00e-01
1/5/9 18 34.2 0 1748942583 6401826 5598945 3.00e-01
1/5/10 18 34.6 0 1847550953 13013135 5598945 3.00e-01
1/5/11 18 34.4 0 1847550953 13013135 5598945 3.00e-01
The data comes in this format but the numbers may be different. What I'm trying to do is have a script that grabs what is in the 3rd column so in this example the 31.2, 34.2, 34.6 and 34.4 then takes those numbers, gives me their average by adding them up and dividing by 4 and then displaying the result in a different textarea box.
I'm also wondering if it can be done in a single script or does it need two scripts. One to parse then the other to calculate and display the average in a textarea.

split the lines and map to match the third column. Then you can find the average and put the result in another textarea:
const input = `1/5/8 18 31.2 0 1847550953 13013135 5598945 3.00e-01
1/5/9 18 34.2 0 1748942583 6401826 5598945 3.00e-01
1/5/10 18 34.6 0 1847550953 13013135 5598945 3.00e-01
1/5/11 18 34.4 0 1847550953 13013135 5598945 3.00e-01`;
const thirdRowMatches = input.split('\n')
.map(line => line.split(/ +/)[2])
const avg = thirdRowMatches.reduce((a, str) => a + Number(str), 0) / thirdRowMatches.length;
document.querySelector('#textarea2').value = avg;
<textarea id="textarea2"></textarea>

Related

Extracting specific Values from a text (txt) file using Java Script (JS)

I'm writing a webpage where you can upload text files and then do some analysis on the file.
the text file is formatted like this:
0 1 475
1 2 437
2 3 553
3 4 500
4 5 612
5 6 491
6 7 444
7 8 544
8 9 491
9 10 595
'*' is only used to make a list on stack-overflow not actually in the text file
I only need to extract the third column(three digits)
and place the values in a variable that I can later use for my analysis.
How can I do this? I can't seem to find an answer with the javascript.
thanks
Here is a way of doing it:
document.querySelector('#fileInput').addEventListener('change', (e) => {
readFile(e.target.files[0]);
});
function readFile(file) {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = function() {
const values = reader.result.split('\n').map(line => (+line.split(' ')[2]));
console.log(values);
};
}
const fileContent = `0 1 475
1 2 437
2 3 553
3 4 500
4 5 612
5 6 491
6 7 444
7 8 544
8 9 491
9 10 595`;
const blob = new Blob([fileContent], {type: 'text/plain'});
readFile(blob);
<input id="fileInput" type="file" onchange="readFile(this.files[0])">
In this example I've used a Blob to imitate a file but you can also use the <input type="file" /> to test the function with a real file.
What this does is to use a FileReader to read a file as text and then parse the content of the file by creating an array that has each line of text in the file as an element (reader.result.split('\n')) and then mapping that array to only keep the last number. That is achieved by splitting the line on every white space character and converting to a number and keeping only the third element from the resulting array (+line.split(' ')[2], the [2] selects the third element and + converts that element to a number).
You could do something like this, where the map function transforms each string into an integer:
const input = ["0 1 475",
"1 2 437",
"2 3 553",
"3 4 500",
"4 5 612",
"5 6 491",
"6 7 444",
"7 8 544",
"8 9 491",
"9 10 595"];
const output = input.map((str) => parseInt(str.split(" ")[2]))
console.log(output);
You can use regular expression:
let str= `0 1 475
1 2 437
2 3 553
3 4 500
4 5 612
5 6 491
6 7 444
7 8 544
8 9 491
9 10 595`
let re=/\d{3}$/gm
console.log(str.match(re))

Need help extracting numbers from string in JavaScript

I need a rock solid RegExp to try and solve some issue with Raphael.js parseStringPath processing regarding Arc path commands and possible others (SnapSVG also inherits the problem). You see, arcTo path command accepts 7 coordinates and settings, but some strings might be malformed due to extreme optimization and the browser doesn't flag them, rather renders them properly. Check Raphael.js demo here.
Have a look at this example, I'm using the RegExp from Raphael.js and a very simplistic example with my own RegExp called incorrectReg, trying to break strings like 000 into [0,0,0] or 011 into [0,1,1].
let spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029",
pathValues = new RegExp(`(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[${spaces}]*,?[${spaces}]*`, `ig`),
incorectReg = new RegExp(`([${spaces}]*0(?=[a-z0-9])|([${spaces}]\\0)*0(?=[a-z0-9]*))`, `ig`); // THIS ONE
function action(){
let input = document.getElementById('input'),
output = document.getElementById('output'),
pathValue = input.getAttribute('d'),
segments = pathValue.replace(/([a-z])/gi,'|$1').split('|').filter(x=>x.trim()),
pathArray = []
segments.map(x=>{
let pathCommand = x[0],
pathParams = x.replace(pathCommand,'').trim()
pathArray.push( [pathCommand].concat(
pathParams.replace(',',' ')
.replace(pathValues,' $1 ')
.replace(incorectReg,'$1 ')
.split(' '))
.filter(x=>x)
);
})
output.setAttribute('d',pathArray.map(x=>x.join(' ')).join(''))
console.table(pathArray)
}
svg {max-width:49%}
<button onclick="action()">Extract</button>
<hr>
<svg viewBox="0 0 16 16">
<path id="input" d="M2,0a2 2 0 00,-2 2a2 2 0 002 2a.5.5 0 011 0z" stroke="red" stroke-width="1px" fill="none"></path>
</svg>
<svg viewBox="0 0 16 16">
<path id="output" d="M0 0" stroke="green" stroke-width="1" fill="none"></path>
</svg>
As you can see in your browser console, we already solve the 000 group (which is obviously not a valid number, boolean, or anything specific), we just have to solve 011 and 11, where all these groups are in fact a string of booleans.
So again, the arcTo path command works with
arcTo -> ['A', rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y]
// str, float, float, float, boolean (0|1), boolean (0|1), float, float
I need a better incorrectReg RegExp and a combination of solutions to properly handle mainly arcTo, and other similar cases. Open to any suggestion.
Thank you
According to the discussion below OP, I propose not to use regexp, but rather a proper parser (or lexer or tokenizer or how to correctly call it).
You can
write your own parser (nice excercise)
use something existing, e.g. I have successfully tried
svg-path-parser.
I am not even sure if such "super"regexp is possible to create.. Anyway you can use "sub"regexp in the parsing process :-)
Just for clarity and serving the community, I will post a working solution, it might help somebody in the future.
Unfortunately the incorrectReg RegExp, good or bad cannot work because it can also change other values as well (EG: M0,11 returns ["M",0,1,1] with the RegExp provided by TheFourthBird), so yea Jan, you were right!
Here's a working solution, please feel free to edit or add more clarity if you like. Once we all agree on a rock solid solution, I will submit a PR to Raphael right after.
let spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029",
pathValues = new RegExp(`(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[${spaces}]*,?[${spaces}]*`, `ig`),
incorrectReg = new RegExp(`(?<=[01${spaces}]+)([01])[${spaces}]*`, `g`); // FIXED ONE
function action(){
let input = document.getElementById('input'),
output = document.getElementById('output'),
pathValue = input.getAttribute('d'),
segments = pathValue.replace(/([a-z])/gi,'|$1').split('|').filter(x=>x.trim()),
pathArray = []
segments.map(x=>{
let pathCommand = x[0],
pathParams = x.replace(pathCommand,'').trim();
pathParams = pathParams.replace(',',' ')
.replace(pathValues,' $1 ')
/* .replace(incorrectReg,' $& ') */
.split(' ').filter(x=>x);
if ( pathCommand.toLowerCase() === 'a' && pathParams.length < 7){
for (let i=0, ln = pathParams.length; i<ln; i++){
if ( (i === 3 || i === 4) && pathParams[i].length > 1 ) {
pathParams = pathParams.slice(0,i) // first part of array
.concat(pathParams[i][0]) // extract largeArcFlag OR sweepFlag
.concat(
pathParams[i].slice(1).replace(/(\-\d|\-\.\d|\.\d*(?=\.))/g,'|$1').split('|'), // get sweepFlag
pathParams.slice(i+1)) // continue after flags
.filter(x=>x) // remove added empty "space" items
ln = pathParams.length // update length
}
}
if (pathParams.length === 7) {
pathArray.push([pathCommand].concat(pathParams.splice(0, 7)));
} else {
throw Error(`arcTo requires 7 coordinates, only ${pathParams.length + ' given: ['+pathParams.join(',')}]`)
}
} else {
pathArray.push( [pathCommand].concat(pathParams) );
}
})
output.setAttribute('d',pathArray.map(x=>x.join(' ')).join(''))
// console.log(pathArray)
}
svg {max-width:49%}
<button onclick="action()">Extract</button>
<hr>
<svg viewBox="0 0 16 16">
<path id="input" d="M2 0a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V2a2 2 0 00-2-2H2zm7.5 11h-4a.5.5 0 01-.5-.5v-4a.5.5 0 011 0v2.793l4.146-4.147a.5.5 0 01.708.708L6.707 10H9.5a.5.5 0 010 1z" fill="red"></path>
</svg>
<svg viewBox="0 0 16 16">
<path id="output" d="M0 0" fill="green"></path>
</svg>

Scraping Javascript-Rendered Content in R from a Webpage without Unique URL

I want to scrape historical results of South African LOTTO draws (especially Total Pool Size, Total Sales, etc.) from the South African National Lottery website. By default one sees links to results for the last ten draws, or one can select a date range to pull up a larger set of links to draws (which will still display only ten per page).
Hovering in the browser over a link e.g. 'LOTTO DRAW 2012' we see javascript:void(); so it is clear that the draw results will be rendered using Javascript. Reading advice on an R Web Scraping Cheat Sheet, I realized that I needed to open Google Chrome Developer tools, then open Network tab, and then click the link to the draw 'LOTTO DRAW 2012'. When I did so, I could see that this url is being called with an initiator
When I right-click on the initiator and select 'Copy Response', I can see the data I need inside a 'drawDetails' object in what appears to be JSON code.
{"code":200,"message":"OK","data":{"drawDetails":{"drawNumber":"2012","drawDate":"2020\/04\/11","nextDrawDate":"2020\/04\/15","ball1":"48","ball2":"6","ball3":"43","ball4":"41","ball5":"25","ball6":"45","bonusBall":"38","div1Winners":"1","div1Payout":"10546013.8","div2Winners":"0","div2Payout":"0","div3Winners":"28","div3Payout":"7676.4","div4Winners":"62","div4Payout":"2751.4","div5Winners":"1389","div5Payout":"206.3","div6Winners":"1872","div6Payout":"133","div7Winners":"28003","div7Payout":"50","div8Winners":"20651","div8Payout":"20","rolloverAmount":"0","rolloverNumber":"0","totalPrizePool":"13280236.5","totalSales":"11610950","estimatedJackpot":"2000000","guaranteedJackpot":"0","drawMachine":"RNG2","ballSet":"RNG","status":"published","winners":52006,"millionairs":1,"gpwinners":"52006","wcwinners":"0","ncwinners":"0","ecwinners":"0","mpwinners":"0","lpwinners":"0","fswinners":"0","kznwinners":"0","nwwinners":"0"},"totalWinnerRecord":{"lottoMillionairs":28716702,"lottoWinners":337285646,"ithubaMillionairs":135763,"ithubaWinners":305615802}},"videoData":[{"id":"1049","listid":"1","parentid":"1","videosource":"youtube","videoid":"chHfFxVi9QI","imageurl":"","title":"LOTTO, LOTTO PLUS 1 AND LOTTO PLUS 2 DRAW 2012 (11 APRIL 2020)","description":"","custom_imageurl":"","custom_title":"","custom_description":"","specialparams":"","lastupdate":"0000-00-00 00:00:00","allowupdates":"1","status":"0","isvideo":"1","link":"https:\/\/www.youtube.com\/watch?v=chHfFxVi9QI","ordering":"10001","publisheddate":"2020-04-11 20:06:17","duration":"182","rating_average":"0","rating_max":"0","rating_min":"0","rating_numRaters":"0","statistics_favoriteCount":"0","statistics_viewCount":"329","keywords":"","startsecond":"0","endsecond":"0","likes":"6","dislikes":"0","commentcount":"0","channel_username":"","channel_title":"","channel_subscribers":"9880","channel_subscribed":"0","channel_location":"","channel_commentcount":"0","channel_viewcount":"0","channel_videocount":"1061","channel_description":"","channel_totaluploadviews":"0","alias":"lotto-lotto-plus-1-and-lotto-plus-2-draw-2012-11-april-2020","rawdata":"","datalink":"https:\/\/www.googleapis.com\/youtube\/v3\/videos?id=chHfFxVi9QI&part=id,snippet,contentDetails,statistics&key=AIzaSyC1Xvk2GUdb_N3UiFtjsgZ-uMviJ_8MFZI"}]}
It is a POST type request, and so I tried to follow this answer, but cannot find onclick values indicating the data submitted with the form. Moreover, the request URL for 'LOTTO DRAW 2012' is identical to that for 'LOTTO DRAW 2011', so there is no unique identifier for the particular draw being passed with the URL itself. Thus it is not clear to me how the unique request for the results of a particular draw is made.
Hence, the smaller question is, given a particular LOTTO draw number or draw date, how does one find out the unique identifier that is used to make the POST request for the data pertaining to that draw specifically?
The larger question is, if one is able to obtain such unique identifiers for all the historical draws, how can one generate the JSON drawDetails object for all the historical draws in turn, or otherwise complete the scraping operation?
You are right - the contents on the page are updated by javascript via an ajax request. The server returns a json string in response to an http POST request. With POST requests, the server's response is determined not only by the url you request, but by the body of the message you send to the server. In this case, your body is a simple form with 3 fields: gameName, which is always LOTTO, isAjax which is always true, and drawNumber, which is the field you want to vary.
If you are using httr, you specify these fields as a named list in the body parameter of the POST function.
Once you have the response for each draw, you will want to parse the json into an R-friendly format such as a list or data frame using a library such as jsonlite. From looking at the structure of this particular json, it makes most sense to extract the component $data$drawDetailsand make that a one-row dataframe. This will allow you to bind several draws together into a single data frame.
Here is a function that does all that for you:
lotto_details <- function(draw_numbers)
{
do.call("rbind", lapply(draw_numbers, function(x)
{
res <- httr::POST(paste0("https://www.nationallottery.co.za/index.php",
"?task=results.redirectPageURL&",
"Itemid=265&option=com_weaver&",
"controller=lotto-history"),
body = list(gameName = "LOTTO", drawNumber = x, isAjax = "true"))
as.data.frame(jsonlite::fromJSON(httr::content(res, "text"))$data$drawDetails)
}))
}
Which you use like this:
lotto_details(2009:2012)
#> drawNumber drawDate nextDrawDate ball1 ball2 ball3 ball4 ball5 ball6
#> 1 2009 2020/04/01 2020/04/04 51 15 7 32 42 45
#> 2 2010 2020/04/04 2020/04/08 43 4 21 24 10 3
#> 3 2011 2020/04/08 2020/04/11 42 43 8 18 2 29
#> 4 2012 2020/04/11 2020/04/15 48 6 43 41 25 45
#> bonusBall div1Winners div1Payout div2Winners div2Payout div3Winners
#> 1 1 0 0 0 0 21
#> 2 22 0 0 0 0 31
#> 3 34 0 0 0 0 21
#> 4 38 1 10546013.8 0 0 28
#> div3Payout div4Winners div4Payout div5Winners div5Payout div6Winners
#> 1 8455.3 60 2348.7 1252 189 1786
#> 2 6004.3 71 2080.6 1808 137.3 2352
#> 3 8584.5 60 2384.6 1405 171.1 2079
#> 4 7676.4 62 2751.4 1389 206.3 1872
#> div6Payout div7Winners div7Payout div8Winners div8Payout rolloverAmount
#> 1 115.2 24664 50 19711 20 3809758.17
#> 2 91.7 35790 50 25981 20 5966533.86
#> 3 100.5 27674 50 21895 20 8055430.87
#> 4 133 28003 50 20651 20 0
#> rolloverNumber totalPrizePool totalSales estimatedJackpot
#> 1 2 6198036.67 9879655 6000000
#> 2 3 9073426.56 11696905 8000000
#> 3 4 10649716.37 10406895 10000000
#> 4 0 13280236.5 11610950 2000000
#> guaranteedJackpot drawMachine ballSet status winners millionairs
#> 1 0 RNG2 RNG published 47494 0
#> 2 0 RNG2 RNG published 66033 0
#> 3 0 RNG2 RNG published 53134 0
#> 4 0 RNG2 RNG published 52006 1
#> gpwinners wcwinners ncwinners ecwinners mpwinners lpwinners fswinners
#> 1 47494 0 0 0 0 0 0
#> 2 66033 0 0 0 0 0 0
#> 3 53134 0 0 0 0 0 0
#> 4 52006 0 0 0 0 0 0
#> kznwinners nwwinners
#> 1 0 0
#> 2 0 0
#> 3 0 0
#> 4 0 0
Created on 2020-04-13 by the reprex package (v0.3.0)
The question already has a satisfactory answer (see above) that I've accepted. I simultaneously arrived at a nearly identical solution; I add it here only because it explicitly covers the full range of available draw numbers and will automatically detect the most recent draw number so that the code can be run 'as is' in the future, provided the National Lottery website design remains the same.
theurl <- "https://www.nationallottery.co.za/index.php?task=results.redirectPageURL&Itemid=265&option=com_weaver&controller=lotto-history"
x <- rvest::html_text(xml2::read_html(theurl))
preceding_string <- "LOTTO, LOTTO PLUS 1 AND LOTTO PLUS 2 DRAW "
drawnums <- as.integer(vapply(gregexpr(preceding_string, x)[[1]] + nchar(preceding_string),
function(k) substr(x, start = k, stop = k + 3), NA_character_))
drawnumrange <- 1506:max(drawnums)
response <- lapply(drawnumrange, function(d) httr::POST(url = theurl,
body = list(gameName = "LOTTO", drawNumber = as.character(d), isAjax =
"true"), encode = "form"))
jsondat <- lapply(response, function(r) jsonlite::parse_json(r)$data$drawDetails)
lottotable <- as.data.frame(do.call(rbind, jsondat))
numericcols <- c(1, 4:32, 36:37)
lottotable[numericcols] <- sapply(lottotable[numericcols], as.numeric)
xlsx::write.xlsx2(lottotable[1:37], "lottotable.xlsx", row.names = FALSE)

Regex for tabular content using Javascript

I am trying to get the specific values from the .txt file using NodeJS. Hope using Regex is the best way to achieve this. So i tried below code from my side, here i can able to get the values from non-tabular area .
var res4 = data.match(/\d*.\d*%id/g);
var res5 = data.match(/\d*\d*k free/g);
console.log(res4); **// [ '93.2%id' ]**
console.log(res5); **//[ '862100k free', '6143996k free' ]**
But when i am trying to get the value inside tabular format of the .txt file , i am unable to do it. Below is the source .txt file for your reference. Here i need cuic_reporting(in command column) and its corresponding %CPU column value for it .Kindly help me in it. Thanks.
top - 02:51:10 up 176 days, 23:47, 1 user, load average: 0.13, 0.09, 0.07
Tasks: 208 total, 1 running, 207 sleeping, 0 stopped, 0 zombie
Cpu(s): 4.7%us, 1.9%sy, 0.1%ni, 93.2%id, 0.0%wa, 0.0%hi, 0.1%si, 0.0%st
Mem: 16336156k total, 15474056k used, 862100k free, 362908k buffers
Swap: 6143996k total, 0k used, 6143996k free, 8415656k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
20967 OSAdmin 30 10 1299m 120m 26m S 35.7 0.8 0:03.47 java
14231 tomcat 20 0 3422m 3.1g 22m S 2.0 20.0 10869:37 cuic_reporting
1 root 20 0 19572 1812 1244 S 0.0 0.0 2:59.65 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root RT 0 0 0 0 S 0.0 0.0 25:22.51 migration/0
4 root 20 0 0 0 0 S 0.0 0.0 2:42.43 ksoftirqd/0
5 root RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/0
6 root RT 0 0 0 0 S 0.0 0.0 0:14.34 watchdog/0
7 root RT 0 0 0 0 S 0.0 0.0 25:47.89 migration/1
8 root RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/1
Thanks !!..
Note that your first pattern can be written as \b\d+(?:\.\d+)?%id\b using a single \d+ matching 1+ digits where you have to escape the dot to match it literally.
If the value can also be without a decimal part, you can make that optional
The second pattern could be written as \b\d+k free\b using a single \d+ to match 1+ digits as well. You could use word boundaries to prevent the word being part of a larger word and get partial matches.
To get the value of cuic_reporting you could use a capturing group and use a repeating group to match the values between the %CPU and cuic_reporting.
(\d+(?:\.\d+)?)(?:[^\S\r\n]+\S+){2}[^\S\r\n]+cuic_reporting\b
Regex demo
let str = ` top - 02:51:10 up 176 days, 23:47, 1 user, load average: 0.13, 0.09, 0.07
Tasks: 208 total, 1 running, 207 sleeping, 0 stopped, 0 zombie
Cpu(s): 4.7%us, 1.9%sy, 0.1%ni, 93.2%id, 0.0%wa, 0.0%hi, 0.1%si, 0.0%st
Mem: 16336156k total, 15474056k used, 862100k free, 362908k buffers
Swap: 6143996k total, 0k used, 6143996k free, 8415656k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
20967 OSAdmin 30 10 1299m 120m 26m S 35.7 0.8 0:03.47 java
14231 tomcat 20 0 3422m 3.1g 22m S 2.0 20.0 10869:37 cuic_reporting
1 root 20 0 19572 1812 1244 S 0.0 0.0 2:59.65 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root RT 0 0 0 0 S 0.0 0.0 25:22.51 migration/0
4 root 20 0 0 0 0 S 0.0 0.0 2:42.43 ksoftirqd/0
5 root RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/0
6 root RT 0 0 0 0 S 0.0 0.0 0:14.34 watchdog/0
7 root RT 0 0 0 0 S 0.0 0.0 25:47.89 migration/1
8 root RT 0 0 0 0 S 0.0 0.0 0:00.00 stopper/1`;
let pattern = /(\d+(?:\.\d+)?)(?:[^\S\r\n]+\S+){2}[^\S\r\n]+cuic_reporting\b/;
console.log(str.match(pattern)[1]);
I give you my opinion about how i would do:
After read the file with fs module for example, i would try to split the data of the txt (either by tabulator('\t') and also line break ('\n' or '\r') if you don't read line by line) and then you can successful apply regex to each element of the resulting array.
I hope it help you!
References:
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/String/split
https://nodejs.org/api/fs.html
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/String/match
You could first refine the top command with parameters so it becomes easier to extract the data you want:
ps -p `pgrep -d "," cuic_reporting` -o %cpu
This will directly output the CPU usage and make parsing the result way easier.

Possible bug in Web Audio API AnalyserNode

I was playing around with Web Audio API and maybe found a bug in the AnalyserNode. Let's say I have two sine oscillators playing at different frequencies, 200 Hz and 8000 Hz respectively. Using two different AnalyserNode(s) I extract the non-zero frequency data from the two oscillators, which are the following (from chrome console):
OSC1 (200 Hz)
Bin 0 value 1
Bin 1 value 3
Bin 2 value 9
Bin 3 value 18
Bin 4 value 30
Bin 5 value 43
Bin 6 value 36
Bin 7 value 159
Bin 8 value 236
Bin 9 value 255
Bin 10 value 255
Bin 11 value 212
Bin 12 value 86
Bin 13 value 46
Bin 14 value 36
Bin 15 value 21
Bin 16 value 8
OSC2 (8000 Hz)
Bin 364 value 6
Bin 365 value 18
Bin 366 value 32
Bin 367 value 46
Bin 368 value 52
Bin 369 value 126
Bin 370 value 224
Bin 371 value 255
Bin 372 value 255
Bin 373 value 226
Bin 374 value 132
Bin 375 value 51
Bin 376 value 47
Bin 377 value 33
Bin 378 value 19
Bin 379 value 7
Now if I change the frequency value of the first oscillator to 8000 Hz (the same of the second oscillator) and extract again the non-zero frequency data I expect to obtain non zero values approximately in the same Bins of the second oscillator (say in the 300-400 range), but strangely there are non zero values also in the Bins in range 0-50 (as when we extracted frequency data using a 200 Hz frequency).
OSC1 (8000 Hz)
Bin 2 value 2
Bin 3 value 11
Bin 4 value 23
Bin 5 value 36
Bin 6 value 29
Bin 7 value 152
Bin 8 value 229
Bin 9 value 255
Bin 10 value 248
Bin 11 value 205
Bin 12 value 79
Bin 13 value 38
Bin 14 value 29
Bin 15 value 14
Bin 16 value 1
Bin 364 value 7
Bin 365 value 19
Bin 366 value 33
Bin 367 value 47
Bin 368 value 50
Bin 369 value 137
Bin 370 value 228
Bin 371 value 255
Bin 372 value 255
Bin 373 value 222
Bin 374 value 121
Bin 375 value 52
Bin 376 value 45
Bin 377 value 31
Bin 378 value 18
Bin 379 value 5
Is this the expected behavior or a bug? It seems not correct to me. I am also not sure if this propagates also when analyzing a standard audio file using for example a requestAnimationFrame loop.
Below the code of the full example.
NB: to extract the frequency data is required to wait a bit before the analyser has finished the Fast Fourier Transform algorithm and the frequency data is available, thus I've used 2 timeOut functions, one for the first extraction of frequency data from osc1 and osc2 and the second to extract again frequency data from osc1 after the oscillator frequency has changed to 8000 Hz).
var AudioContext = window.AudioContext || window.webkitAudioContext;
var ctx = new AudioContext();
// first oscillator (200 Hz)
var osc1 = ctx.createOscillator();
osc1.frequency.value = 200;
var analyser1 = ctx.createAnalyser();
var gain1 = ctx.createGain();
gain1.gain.value = 0;
osc1.connect(analyser1);
analyser1.connect(gain1);
gain1.connect(ctx.destination);
// second oscillator (8000 Hz)
var osc2 = ctx.createOscillator();
osc2.frequency.value = 8000;
var analyser2 = ctx.createAnalyser();
var gain2 = ctx.createGain();
gain2.gain.value = 0;
osc2.connect(analyser2);
analyser2.connect(gain2);
gain2.connect(ctx.destination);
// start oscillators
osc1.start();
osc2.start();
// get frequency data
var freqData1 = new Uint8Array(analyser1.frequencyBinCount);
var freqData2 = new Uint8Array(analyser2.frequencyBinCount);
setTimeout(function() {
analyser1.getByteFrequencyData(freqData1);
analyser2.getByteFrequencyData(freqData2);
console.log("OSC1 (200 Hz)");
printNonZeroFreqData(freqData1);
console.log("OSC2 (8000 Hz)");
printNonZeroFreqData(freqData2);
// change frequency of osc1 to 8000 Hz
osc1.frequency.value = 8000;
// wait a bit, then extract again frequency data from osc1
setTimeout(function() {
freqData1 = new Uint8Array(analyser1.frequencyBinCount);
analyser1.getByteFrequencyData(freqData1);
console.log("OSC1 (8000 Hz)");
printNonZeroFreqData(freqData1);
}, 500);
}, 500);
// print non zero frequency values
function printNonZeroFreqData(arr) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i] != 0) {
console.log("Bin " + i, "\tvalue " + arr[i]);
}
}
console.log("");
}
This is expected. According to the spec, successive calls to extract the frequency data combines the data from the current call with a history of the data from previous calls. If we want to see the frequency data only from the current time, set smoothingTimeConstant to 0.
smoothingTimeConstant on Mozilla Developer Network

Categories