Basically, I am using Google's Teachable Machine for something, and I am making a p5.js webpage to go around the data. I have some if statements for a timer because I want to only be changing it if it has been a few seconds (note that I haven't implemented that yet, so that won't work yet even if the code starts to work). I have console.log() statements to help with things, and I am using booleans to help with my if statements just in case that helps for some reason (neither does, but I know doing console.log(myBool) returns the value being true when it should be, but the if statements still don't work. Anyways, enough rambling. Here is my code:
<div>
Teachable Machine Audio Model - p5.js and ml5.js
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/addons/p5.dom.min.js"></script>
<script src="https://unpkg.com/ml5#0.4.3/dist/ml5.min.js"></script>
<script type="text/javascript">
// Global variable to store the classifier
let classifier;
// Label
let label = 'listening...';
// Teachable Machine model URL:
let soundModel = 'https://teachablemachine.withgoogle.com/models/khgwJCtEk/';
function preload() {
// Load the model
classifier = ml5.soundClassifier(soundModel + 'model.json');
}
function setup() {
createCanvas(320, 240);
// Start classifying
// The sound model will continuously listen to the microphone
classifier.classify(gotResult);
}
function draw() {
background(0);
// Draw the label in the canvas
fill(255);
textSize(32);
textAlign(CENTER, CENTER);
text(label, width / 2, height / 2);
}
let firstTime = true;
// The model recognizing a sound will trigger this event
function gotResult(error, results) {
if (error) {
console.error(error);
return;
}
// The results are in an array ordered by confidence.
console.log(results[0]);
console.log(results[0]["confidence"]);
var startTimer = results[0]["confidence"] > 0.8 && firstTime == true;
var continueTimer = results[0]["confidence"] > 0.8 && firstTime == false;
var newThing = results[0]["label"] != label;
if (startTimer == true) {
var milliseconds = now.getMilliseconds();
label = results[0].label;
firstTime = false;
console.log("First time!")
}
else if (continueTimer == true) {
if (newThing == false) {
console.log("Not first.")
var newTimeout = 1000 - milliseconds;
this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout);
console.log(newTimeout);
}
else if (newThing == true) {
label = results[0].label;
firstTime = true;
}
}
}
</script>
Any idea why my if statements aren't working? If it is a silly problem, sorry, silly problems happen often for me.
When running your code, I got an error:
Uncaught (in promise) ReferenceError: now is not define
It turns out when you do:
var milliseconds = now.getMilliseconds();
in one of your if statements, you are using an undefined now. You can try adding a definition of now before that line:
var now = new Date();
var milliseconds = now.getMilliseconds();
This should fix the error, and the code should start to work (although other problems may still occur).
Related
I want to be able to change the value of a global variable when it is being used by a function as a parameter.
My javascript:
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
playAudio(audioFilePitch01, canPlayPitch01);
});
My HTML:
<body>
<button id="btnPitch01">Play Pitch01</button>
<button id="btnPitch02">Play Pitch02</button>
<script src="js/js-master.js"></script>
</body>
My scenario:
I'm building a Musical Aptitude Test for personal use that won't be hosted online. There are going to be hundreds of buttons each corresponding to their own audio files. Each audio file may only be played twice and no more than that. Buttons may not be pressed while their corresponding audio files are already playing.
All of that was working completely fine, until I optimised the function to use parameters. I know this would be good to avoid copy-pasting the same function hundreds of times, but it has broken the solution I used to prevent the audio from being played more than once. The "canPlayPitch01" variable, when it is being used as a parameter, no longer gets incremented, and therefore makes the [if (canPlay < 2)] useless.
How would I go about solving this? Even if it is bad coding practise, I would prefer to keep using the method I'm currently using, because I think it is a very logical one.
I'm a beginner and know very little, so please forgive any mistakes or poor coding practises. I welcome corrections and tips.
Thank you very much!
It's not possible, since variables are passed by value, not by reference. You should return the new value, and the caller should assign it to the variable.
function playAudio(audioFile, canPlay) {
if (canPlay < 2 && audioFile.paused) {
canPlay = canPlay + 1;
audioFile.play();
} else {
if (canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
return canPlay;
};
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
btnPitch01.addEventListener("click", function() {
canPlayPitch01 = playAudio(audioFilePitch01, canPlayPitch01);
});
A little improvement of the data will fix the stated problem and probably have quite a few side benefits elsewhere in the code.
Your data looks like this:
const btnPitch01 = document.getElementById("btnPitch01");
const audioFilePitch01 = new Audio("../aud/Pitch01.wav");
var canPlayPitch01 = 0;
// and, judging by the naming used, there's probably more like this:
const btnPitch02 = document.getElementById("btnPitch02");
const audioFilePitch02 = new Audio("../aud/Pitch02.wav");
var canPlayPitch02 = 0;
// and so on
Now consider that global data looking like this:
const model = {
btnPitch01: {
canPlay: 0,
el: document.getElementById("btnPitch01"),
audioFile: new Audio("../aud/Pitch01.wav")
},
btnPitch02: { /* and so on */ }
}
Your event listener(s) can say:
btnPitch01.addEventListener("click", function(event) {
// notice how (if this is all that's done here) we can shrink this even further later
playAudio(event);
});
And your playAudio function can have a side-effect on the data:
function playAudio(event) {
// here's how we get from the button to the model item
const item = model[event.target.id];
if (item.canPlay < 2 && item.audioFile.paused) {
item.canPlay++;
item.audioFile.play();
} else {
if (item.canPlay >= 2) {
alert("This audio has already been played twice.");
} else {
alert("Please wait for the audio to finish playing.");
};
};
};
Side note: the model can probably be built in code...
// you can automate this even more using String padStart() on 1,2,3...
const baseIds = [ '01', '02', ... ];
const model = Object.fromEntries(
baseIds.map(baseId => {
const id = `btnPitch${baseId}`;
const value = {
canPlay: 0,
el: document.getElementById(id),
audioFile: new Audio(`../aud/Pitch${baseId}.wav`)
}
return [id, value];
})
);
// you can build the event listeners in a loop, too
// (or in the loop above)
Object.values(model).forEach(value => {
value.el.addEventListener("click", playAudio)
})
below is an example of the function.
btnPitch01.addEventListener("click", function() {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio(audioFilePitch01, canPlayPitch01);
this.dataset.numberOfPlays++;
});
you would want to select all of your buttons and assign this to them after your html is loaded.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
const listOfButtons = document.getElementsByClassName('pitchButton');
listOfButtons.forEach( item => {
item.addEventListener("click", () => {
if ( this.dataset.numberOfPlays >= this.dataset.allowedNumberOfPlays ) return;
playAudio("audioFilePitch" + this.id);
this.dataset.numberOfPlays++;
});
what i want to achieve, is try to increase the precison of the values returned by the heart beat sensor of a Tizen smartwatch.
The values are Float64 numbers, since the language is Javascript.
I tried to use a function like this:
function strip(interval) {
return (parseFloat(interval).toPrecision(4));
}
but with no success. Maybe i'm doing something wrong, like doing some programming mistakes, i really don't know. Apparently, the IDE compile and build the package to install with no problem, but i can't see something different with or without this function included.
I will post my entire code below. Please check when is created the function strip . I've used the escamotage if (interval !== 0) {
interval_screen = interval;
} because i don't want the zeros to be printed. Please note that i want the variable streamed to the ROS topic HeartRateInterval to remain a Float; this is why i've also used the parseFloat function.
Thank you!
Code :
document.addEventListener('tizenhwkey', function(e) {
if(e.keyName === "back")
window.webapis.motion.stop("HRM");
tizen.application.getCurrentApplication().exit();
});
function Connect(){
var ip;
var connection=false;
var interval_screen = 0;
if (document.getElementById("ip").value==="")
{
ip="10.42.0.1";
}
else
{
ip=document.getElementById("ip").value;
}
var ros = new ROSLIB.Ros({
url : 'ws://' + ip +':9090'
});
ros.on('connection', function() {
connection=true;
document.getElementById("Connection_status").setAttribute("color","green");
document.getElementById("Connection_status").innerHTML = 'Connected';
tizen.power.request("SCREEN", "SCREEN_DIM");
});
ros.on('error', function(error) {
document.getElementById("Connection_status").setAttribute("color","orange");
document.getElementById("Connection_status").innerHTML = 'Error';
});
ros.on('close', function() {
document.getElementById("Connection_status").setAttribute("color","red");
document.getElementById("Connection_status").innerHTML = 'Unconnected';
connection=false;
tizen.power.release("SCREEN");
});
var RatePub = new ROSLIB.Topic({
ros : ros,
name : '/HeartRateData',
messageType : 'std_msgs/Float64'
});
var IntervalPub = new ROSLIB.Topic({
ros : ros,
name : '/HeartRateInterval',
messageType : 'std_msgs/Float64'
});
window.webapis.motion.start("HRM", onchangedCB);
function onchangedCB(hrmInfo)
{
var rate = hrmInfo.heartRate;
document.getElementById("mytext").innerHTML = 'Heart Rate= ' + rate + ' bpm';
var interval = hrmInfo.rRInterval/1000;
function strip(interval) {
return (parseFloat(interval).toPrecision(4));
}
if (interval !== 0) {
interval_screen = interval;
}
document.getElementById("mytext1").innerHTML = 'RR Interval= ' + interval_screen + ' s';
var Float64 = new ROSLIB.Message({
data:rate
});
if(connection===true)
{
RatePub.publish(Float64);
}
else
{
document.getElementById("mytext").innerHTML = 'Heart Rate = 0 bpm';
}
var Float64 = new ROSLIB.Message({
data:interval
});
if(connection===true)
{ if (interval !== 0) {
IntervalPub.publish(Float64);
}
else {
}
}
else
{
document.getElementById("mytext1").innerHTML = 'RR Interval = 0 s';
}
}}
Am I missing something here, but I can not find where you actually call that new function?
And why do you create it inline inside the onchangedCB function?
It looks as if you expected that function to be called because you declare it there and call the parameter the same as the interval variable. Which will not work (as far as I know in any programming language).
Then what I would try is call that function parseFloat(interval).toPrecision
directly instead of putting it in another function.
But what I'm far more interested in is:
here hrmInfo.rRInterval/1000
the orginal value is devived by a thousand.
Remove that division (like this var interval = hrmInfo.rRInterval;) and see if there actually are more numbers where the decimal point would be.
I can not make it up from your example, but if the value normally is something like 120 per minute. And you want to know if there are more precise values behind that, then the value should now look something like 1200054 if it is all zeroes like 120000 all the time, then the systems creating that event does not give off a more precise measure.
I am working on a bit of code in Javascript that polls a time consuming process that is running in a webservice and returns the status every two seconds. The processPoll function is never getting hit and I can not figure out why the setInterval does not work. I think I have the scope right so I'm not sure why processPoll does not start.
var processId;
var timerId;
function processStartReturn(retVal) {
if ((retVal != null) && (retVal != "")) {
processId = retVal;
timerId = setInterval(processPoll, 2000);
alert(processId); --> alerts correct Id
}
}
function processPoll() {
alert("This alert never shows up!");
WebService.MyFunction(processId, 0);
}
function startPoll() {
var appName = document.getElementById("appName").value;
var threadId = appName + "object";
processStartReturn(threadId);
}
Edit: I have added the startPoll() function that is started with an onclientclick event.
I'm facing a problem with canvas. No errors is returned. I'm trying to make a loader, that load every ressources like pictures, sounds, videos, etc, before the start of application. The loader must draw the number of ressourses loaded dynamically.
But at this moment, my loader has the result to freeze the browser until it draws the total of ressources loaded.
Tell me if i'm not clear :)
This is the code :
function SimpleLoader(){
var ressources ;
var canvas;
var ctx;
this.getRessources = function(){
return ressources;
};
this.setAllRessources = function(newRessources){
ressources = newRessources;
};
this.getCanvas = function(){
return canvas;
};
this.setCanvas = function(newCanvas){
canvas = newCanvas;
};
this.getCtx = function(){
return ctx;
};
this.setCtx = function(newCtx){
ctx = newCtx;
};
};
SimpleLoader.prototype.init = function (ressources, canvas, ctx){
this.setAllRessources(ressources);
this.setCanvas(canvas);
this.setCtx(ctx);
};
SimpleLoader.prototype.draw = function (){
var that = this;
this.getCtx().clearRect(0, 0, this.getCanvas().width, this.getCanvas().height);
this.getCtx().fillStyle = "black";
this.getCtx().fillRect(0,0,this.getCanvas().width, this.getCanvas().height)
for(var i = 0; i < this.getRessources().length; i++){
var data = this.getRessources()[i];
if(data instanceof Picture){
var drawLoader = function(nbLoad){
that.getCtx().clearRect(0, 0, that.getCanvas().width, that.getCanvas().height);
that.getCtx().fillStyle = "black";
that.getCtx().fillRect(0,0, that.getCanvas().width, that.getCanvas().height);
that.getCtx().fillStyle = "white";
that.getCtx().fillText("Chargement en cours ... " + Number(nbLoad) +"/"+ Number(100), that.getCanvas().width/2, 100 );
}
data.img = new Image();
data.img.src = data.src;
data.img.onload = drawLoader(Number(i)+1); //Update loader to reflect picture loading progress
} else if(data instanceof Animation){
/* Load animation */
} else if(data instanceof Video){
/* Load video */
} else if(data instanceof Sound){
/* Load sound */
}else {
}
}
};
So with this code, all resources are loaded, but i want to display the progress of loading. Some idea of what i missed?
You are "busy-looping" in the loader so the browser doesn't get a chance to redraw/update canvas.
You can implement a setTimeout(getNext, 0) or put the draw function outside polling current status in a requestAnimationFrame loop instead. I would recommend the former in this case.
In pseudo code, this is one way to get it working:
//Global:
currentItem = 0
total = numberOfItems
//The loop:
function getNextItem() {
getItem(currentItem++);
drawProgressToCanvas();
if (currentItem < total)
setTimeout(getNextItem(), 0);
else
isReady();
}
getNextItem(); //start the loader
Adopt as needed.
The setTimeout with a value of 0 will cue up a call next time there is time available (ie. after a redraw, empty event stack etc.). isReady() here is just one way to get to the next step when everything is loaded. (If you notice any problems using 0, try to use for example 16 instead.)
Using requestAnimationFrame is a more low-level and efficient way of doing it. Not all browsers support it at the moment, but there are poly-fills that will get you around that - For this kind of usage it is not so important, but just so you are aware of this option as well (in case you didn't already).
I was trying to show a text gradually on the screen (like marquee). e.g. H.. He.. Hell.. Hello. when I'm tracing it in debug in VS2010 it's working! but when it's actually running it shows the whole sentence at once.
I made a certain "delay" for about 3 seconds between each letter so it would suppose to take a while, but in reality it's shows everything immediately.
Who's the genius to solve this mystery? (please don't give me advices how to create the marquee effect, it's not the issue anymore. now it's just a WAR between me and javascript!) I'm assuming that it has to do with synchronization when calling function from function?
Thanks to whomever will help me get my sanity back.
you can download the code from here (VS project):
http://pcgroup.co.il/downloads/misc/function_from_function.zip
or view it here:
<body>
<script type="text/javascript">
//trying to display this source sentence letter by letter:
var source = "hi javascript why are you being such a pain";
var target = "";
var pos = 0;
var mayGoOn = false;
//this function calls another function which suppose to "build" the sentence increasing index using the global var pos (it's even working when following it in debug)
function textticker() {
if (pos < source.length) {
flash();
if (mayGoOn == true) {
pos++;
mayGoOn = false;
document.write(target);
textticker();
}
}
}
function flash() {
//I tried to put returns everywhere assuming that this may solve it probably one of them in not necessary but it doesn't solve it
if (mayGoOn == true) { return; }
while (true) {
var d = new Date();
if (d.getSeconds() % 3 == 0) {
//alert('this suppose to happen only in about every 3 seconds');
target = source.substring(0, pos);
mayGoOn = true;
return;
}
}
}
textticker();
</script>
You're obviously doing it wrong. Take a look at this.
var message = "Hello World!";
function print(msg, idx) {
if(!idx) {
idx = 0;
}
$('#hello').html(msg.substring(0, idx));
if(idx < msg.length) {
setTimeout(function() { print(msg, idx + 1) }, 200);
}
}
print(message);
Demo: http://jsbin.com/evehus