I'm trying to create a 'beats per minute' (BPM) calculator, identical (for now) to the one you can find here. But for some reason, when I use the BPM calculator at that link on a test song, it gets within 1 BPM of the actual value of 85.94 within of 7 keypresses and just gets more accurate from there, ending within 0.05 of the actual BPM, whereas with my (essentially identically-coded) Vue.js version, it starts much higher (182-->126-->110) and goes down from there, but even after 60 keypresses it's still off by ~2 BPM, and after a full song, it was still off by about 0.37 BPM.
Here's the code for the plain-JavaScript version at that link:
var count = 0;
var msecsFirst = 0;
var msecsPrevious = 0;
function ResetCount()
{
count = 0;
document.TAP_DISPLAY.T_AVG.value = "";
document.TAP_DISPLAY.T_TAP.value = "";
document.TAP_DISPLAY.T_RESET.blur();
}
function TapForBPM(e)
{
document.TAP_DISPLAY.T_WAIT.blur();
timeSeconds = new Date;
msecs = timeSeconds.getTime();
if ((msecs - msecsPrevious) > 1000 * document.TAP_DISPLAY.T_WAIT.value)
{
count = 0;
}
if (count == 0)
{
document.TAP_DISPLAY.T_AVG.value = "First Beat";
document.TAP_DISPLAY.T_TAP.value = "First Beat";
msecsFirst = msecs;
count = 1;
}
else
{
bpmAvg = 60000 * count / (msecs - msecsFirst);
document.TAP_DISPLAY.T_AVG.value = Math.round(bpmAvg * 100) / 100;
document.TAP_DISPLAY.T_WHOLE.value = Math.round(bpmAvg);
count++;
document.TAP_DISPLAY.T_TAP.value = count;
}
msecsPrevious = msecs;
return true;
}
document.onkeypress = TapForBPM;
// End -->
And here's my version:
computed: {
tappedOutBpm: function() {
let totalElapsedSeconds = (this.timeOfLastBpmKeypress - this.timeOfFirstBpmKeypress) / 1000.0
let bpm = (this.numberOfTapsForBpm / totalElapsedSeconds) * 60.0
return Math.round(100*bpm)/100;
},
},
methods: {
tapForBPM: function() {
let now = new Date;
now = now.getTime();
// let now = window.performance.now()
if (this.timeOfFirstBpmKeypress === 0 || now - this.timeOfLastBpmKeypress > 5000) {
this.timeOfFirstBpmKeypress = now
this.timeOfLastBpmKeypress = now
this.numberOfTapsForBpm = 1
} else {
this.timeOfLastBpmKeypress = now
this.numberOfTapsForBpm++
}
}
}
I figured it out by stepping through both of our code.
The problem was that I was setting the number of taps to 1 as soon as the user tapped the key the first time, when in reality it's not taps that I want to count, but beats, and the first beat requires not one tap, but two: the start and the end of that beat. So what I should do is rename the variable to numberOfTappedOutBeats and set it to 0 after the first tap rather than 1.
Related
I am trying to make a counter that counts to big numbers such as 6 billion (like google's random number generator) but the browser freezes.
var counter = document.querySelector("#blahblahblah");
var nb = 0;
var ne = 6_000_000_000;
for (;nb<=ne;nb++) {
counter.innerHTML = nb;
};
The best thing you can do is use requestAnimationFrame. This represents the fastest rate that you can update the DOM.
Use the callback to update your element text to the number proportional to the time allowed; 3 seconds (a few) from your question title
const runCounter = (num, timeout, el) => {
let start;
const step = (ts) => {
if (!start) {
start = ts; // record the first iteration timestamp
}
const progress = (ts - start) / timeout; // how far into the time span
if (progress < 1) {
el.textContent = Math.floor(num * progress);
requestAnimationFrame(step); // request the next frame
} else {
el.textContent = num; // finish
}
};
requestAnimationFrame(step);
};
runCounter(6_000_000_000, 3000, document.getElementById("blahblahblah"));
<output id="blahblahblah">0</output>
In JAVASCRIPT:
If I have a variable which value is constantly changing (100+ times a second). How do I 'record' a specific value at a specific point in time?
Added to this, how do I base this point in time off of another variable of which value has changed?
This needs to be strictly in JavaScript. I've looked at the onChange() method, but I'm unsure if I have to use this in conjunction with HTML for it to work. If not, could someone give me an example where this is not the case?
Cheers
I'm not 100% clear on what you're trying to do, but as Ranjith says you can use setTimeout to run arbitrary code at some (approximate) future time.
This example could likely be improved if I had a bit more detail about what you're doing.
If you're in a node environment you might consider using an event emitter to broadcast changes instead of having to have the variable in scope. (This isn't particularly hard to do in a browser either if that's where you are.)
The html/css parts of this are just for displaying the values in the example; not necessary otherwise.
const rand = document.getElementById('rand');
const snapshot = document.getElementById('snapshot');
let volatile = 0;
// update the value every ~100ms
setInterval(() => {
// assign a new random value
volatile = Math.random();
// display it so we can see what's going on
rand.innerText = volatile;
}, 100);
// do whatever you want with the snapshotted value here
const snap = () => snapshot.innerText = volatile;
// grab the value every 2 seconds
setInterval(snap, 2000);
div {
margin: 2rem;
}
<div>
<div id="rand"></div>
<div id="snapshot"></div>
</div>
Ok - well you can poll variable changes ... even though you can use setters...
Lets compare:
Polling:
let previous;
let watched = 0;
let changes = 0;
let snap = () => previous = watched !== previous && ++changes && watched || previous;
let polling = setInterval(snap, 100);
let delta = 1000 * 2
let start = Date.now();
let last = start;
let now;
let dt = 0
while(start + delta > Date.now()){
now = Date.now();
dt += now - last;
last = now;
if(dt > 100){
watched++;
dt = 0;
}
}
document.getElementsByTagName('h1')[0].innerText = (changes === 0 ? 0 : 100 * watched / changes) + "% hit"
if(watched - changes === watched){
throw Error("polling missed 100%");
}
<h1><h1>
emitting:
const dataChangeEvent = new Event("mutate");
const dataAccessEvent = new Event("access");
// set mock context - as it is needed
let ctx = document.createElement('span');
// add watchable variable
add('watched', 0);
//listen for changes
let changes = 0;
ctx.addEventListener('mutate', () => changes++);
let delta = 1000 * 2
let start = Date.now();
let last = start;
let now;
let dt = 0
while(start + delta > Date.now()){
now = Date.now();
dt += now - last;
last = now;
if(dt > 100){
ctx.watched++;
dt = 0;
}
}
document.getElementsByTagName('h1')[0].innerText = (changes === 0 ? 0 : 100 * ctx.watched / changes) + "% hit"
if(ctx.watched - changes === ctx.watched){
throw Error("trigger missed 100%");
}
function add(name, value){
let store = value
Object.defineProperty(ctx, name, {
get(){
ctx.dispatchEvent(dataAccessEvent, store)
return store;
},
set(value){
ctx.dispatchEvent(dataChangeEvent, {
newVal: value,
oldVal: store,
stamp: Date.now()
});
store = value;
}
})
}
<h1></h1>
The usage of a while loop is on purpose.
I'm trying to write an if/else function for a simple game but I'm new to coding.
Chapters = 0;
Books = 0;
Pens = 1;
WarehouseRoom = 50;
function ChaptersUp(number) {
if (Pens > 0) {
if ((Chapters + number) <= 9) {
Chapters = Chapters + number;
document.getElementById("Chapters").innerHTML = Chapters;
}
else {
ChaptersOver = (Chapters + number - 10);
if (Books < WarehouseRoom) {
if (ChaptersOver <= 9) {
Books = Books + 1;
Chapters = ChaptersOver;
document.getElementById("Chapters").innerHTML = Chapters;
document.getElementById("Books").innerHTML = Books;
}
else {
BooksOver = Math.floor(ChaptersOver / 10);
Books = Books + BooksOver + 1;
Chapters = (ChaptersOver - (BooksOver * 10));
document.getElementById("Chapters").innerHTML = Chapters;
document.getElementById("Books").innerHTML = Books;
}
}
}
}
}`
I want the function to run up to the point where the Warehouse is full. Currently, if I add 11 Books (110 Chapters) at a time, the function will stop operating at 55 books, but I've already went over the limit.
Question : How can I make it stop at exactly the amount equal to WarehouseRoom?
You should try a for statment.
for(int i = 1; i > warehouseFull; i++)
{
}
i is used to for the repeat and the loop will repeat till it is equal to warehouseFull. The i++ on the end is there so that once the loop is done it adds 1 to i. You could do the same backwards if you lose x amount of books. also you don't have to declare i inside the for statesmen but if you are going to use many for statement then it will make it easier so you don't have to switch letters or declare i = 0;.
I wrote a function that takes the number (amount) of songs, the object of songs and makes a new object, which includes new amount of songs chosen randomly.
The problem is that when I start it with number of 3, for example, it works correctly, but then I start this function with less number and it shows me the previous result with 3 songs instead of 2. If then I start it with number 5 I want to see 5 new song, but instead I get my previous 3 songs and 2 new. Why It doesn't choose randomly again?
$scope.getRandom = function(max) {
return Math.floor(Math.random() * (max + 1));
};
$scope.chooseSongs = function(hide) {
if (hide) {
$scope.hideDiv();
}
if ($scope.number > songs.length) {
$scope.number = songs.length;
}
while ($scope.newSongs.length < $scope.number) {
$scope.rand = $scope.getRandom(songs.length - 1);
$scope.songName = songs[$scope.rand].name;
if ($scope.newSongs.indexOf($scope.songName) == -1) {
$scope.newSongs.push($scope.songName);
}
}
$scope.number = 1;
};
When you use .push on an array, it does what it says: it "pushes" the new data in the back of the array, ignoring the fact if there already is data or not.
So you never clear your array, and keep "pushing" new data to the back.
An easy solution for this is adding $scope.newSongs = [] $scope.newSongs.length = 0; at the start of your function:
$scope.getRandom = function(max) {
return Math.floor(Math.random() * (max + 1));
};
$scope.chooseSongs = function(hide) {
$scope.newSongs.length = 0; //$scope.newSongs = [];
if (hide) {
$scope.hideDiv();
}
if ($scope.number > songs.length) {
$scope.number = songs.length;
}
while ($scope.newSongs.length < $scope.number) {
$scope.rand = $scope.getRandom(songs.length - 1);
$scope.songName = songs[$scope.rand].name;
if ($scope.newSongs.indexOf($scope.songName) == -1) {
$scope.newSongs.push($scope.songName);
}
}
$scope.number = 1;
};
Edit: Updated after an interesting comment from Deblaton Jean-Philippe. If you just reassign $scope.newSongs, AngularJs won't automatically update the view, if you instead clear the length, it will.
I'm using the Scripter MidiFX in MainStage 3 (same as LogicPro X) to create a custom arpeggiator with javascript since I needed more control, so it seemed logical to use GetTimingInfo() to get the current beat inside of the ProcessMIDI() function to trigger the MIDI notes, as I saw in their examples. Unfortunately this pegs the CPU even on my Core i7 MacBook Pro, and I'm using a MacMini with a Core 2 Duo processor at actual shows.
I was able to write my own code to calculate the current beat using new Date() and then only using GetTimingInfo() to get the current tempo when a new note is pressed, but even that wasn't keeping the CPU where I'd like it to be.
When I don't include "NeedsTimingInfo=true" and just hard code the tempo everything works great, but this is a lot of extra code and makes more sense to use the built in functions.
Here's a simple sequencer example that causes this problem... am I doing something wrong? This happen even if I use a counter only run ProcessMIDI() on every 16th call!
NeedsTimingInfo = true;
var startBeat = -1;
var startPitch = 0;
var lastBeat = -1;
var currentStep = 0;
// melody test
var steps = [
0, 0, 0, 0, 0, 0, 0, 2,
0, 0, 0, 0, 0, 0, 2, 5
];
function HandleMIDI(e) {
if (e instanceof NoteOn) {
if (startBeat > 0) return;
currentStep = 0;
startPitch = e.pitch;
var info = GetTimingInfo();
lastBeat = startBeat = info.blockStartBeat;
doNote(e.pitch);
}
else if (e instanceof NoteOff) {
if (e.pitch == startPitch) {
startBeat = -1;
}
}
else {
e.send();
}
}
function doNote(pitch) {
var adjustment = 0;
if (currentStep < steps.length) {
adjustment = steps[currentStep];
}
var p = pitch + adjustment;
var on = new NoteOn;
on.pitch = p;
on.send();
var off = new NoteOff;
off.pitch = p;
off.sendAfterBeats(0.9);
}
function ProcessMIDI() {
var info = GetTimingInfo();
if (!info.playing) return;
if (startBeat < 0) return;
var beat = info.blockStartBeat;
if (beat - lastBeat >= 1) {
currentStep++;
doNote(startPitch);
lastBeat = beat;
}
}
What is your I/O Buffer Size set to in Preferences>Audio>Advanced Settings ? The smaller the buffer size, the more CPU is required. I assume that since you are using MainStage for live use, you have a very low setting to minimize your latency.
I tried running your code with a buffer size of 16, and MS stuttered and maxed out the CPU. 64 handled it better (the CPU meter spiked, but played without any hiccups). Tested on a 2009 MacBook Pro 3.06 Core 2 Duo.
You may have to live with a little more latency in order for Scripter to run smoothly. Your code itself, is solid.
The issue went away with MainStage 3.0.3, which I was able to find using TimeMachine.
I did create a custom Javascript implementation of their timing for 3.0.4, which did help quite a bit, but is an extra 100 lines or so of code (below). If anyone else runs into this problem, I would suggest the downgrade instead.
/**---[lib/TimingInfo.js]---*/
/**
* Constructor accepts the tempo for beat syncing and starts the clock
* to the current time.
*
* #param tempo the tempo of the song (default: 120bpm)
*/
function TimingInfo(tempo) {
this.schedule = [];
this.setTempo(tempo);
this.reset();
}
/**
* Sets the tempo and computes all times associated, assuming a 4/4
* beat structure.
*
* #param tempo the current tempo in beats per minute.
*/
TimingInfo.prototype.setTempo = function(tempo) {
this.tempo = tempo || 120;
this.msecPerBeat = (60 / this.tempo) * 1000;
};
/**
* Resets the beatsync to be relative to the current time.
*/
TimingInfo.prototype.reset = function() {
this.startTime = new Date().getTime();
this.update();
// trigger all scheduled messages immediately. TODO: note off only?
this._sendScheduled(null);
};
/**
* Uses the current time to update what the current beat is and all
* related properties. Any scheduled actions are performed if their
* time has passed.
*/
TimingInfo.prototype.update = function() {
var now = new Date().getTime();
this.elapsedMsec = now - this.startTime;
this.beat = this.elapsedMsec / this.msecPerBeat;
this._sendScheduled(this.beat);
};
/**
* Schedules a midi message to be sent at a specific beat.
* #param e MIDI event to schedule
* #param beat the beat number to send it on
*/
TimingInfo.prototype.sendAtBeat = function(e, beat) {
if (e == null) return;
// insert in-order into schedule
var insertAt = 0;
for (var i = 0; i < this.schedule.length; i++) {
if (this.schedule[i].beat > beat) {
insertAt = i;
break;
}
}
this.schedule.splice(insertAt, 0, {e:e, beat:beat});
};
/**
* Schedules a midi message relative to current beat.
*/
TimingInfo.prototype.sendAfterBeats = function(e, deltaBeats) {
this.sendAtBeat(e, this.beat + deltaBeats);
};
/**
* Sends all messages scheduled on or before a given beat. If not
* supplied all scheduled items are sent.
*
* #param atBeat beat to compare all scheduled events against (default: all)
*/
TimingInfo.prototype._sendScheduled = function(atBeat) {
// send all items on or before the given beat
var sent = 0;
for (var i = 0; i < this.schedule.length; i++) {
if (!atBeat || this.schedule[i].beat <= atBeat) {
this.schedule[i].e.send();
sent++;
}
else {
break;
}
}
// remove sent items
this.schedule.splice(0, sent);
};
var _timing = null;
/**
* Replacement for GetTimingInfo() that calls update() to handling
* any scheduled actions.
*/
function GetNewTimingInfo() {
if (_timing == null) {
_timing = new TimingInfo();
}
_timing.update();
return _timing;
}