p5js leaving traces on canvas - p5.js

I am doing an audio visualisation application that changes the size of a shape based on the values of meyda's zcr feature. However once the shape is drawn, it still leaves a traces of the previous value on the canvas. Is the a way I can remove these traces?
I assigned the zcr values to the shapes height however the drawn shape does not refresh its appearance even though the values are changed.
function setup() {
createCanvas(800,600);
background(180);
playStopButton = createButton('play');
playStopButton.position(20, 20);
playStopButton.mousePressed(playStopSound);
amplitude = new p5.Amplitude();
rectH = 0;
if (typeof Meyda === "undefined"){
console.log("meyda could not be found");
} else {
analyzer = Meyda.createMeydaAnalyzer({
"audioContext": getAudioContext(),
"source": mySound,
"bufferSize": 512,
"featureExtractors": ["zcr"],
"callback": features => {
console.log(features);
rectH = features.zcr;
}
});
}
}
function draw() {
rectMode(RADIUS);
rect(80,300, 80, rectH);
console.log(rectH);
}
draw and set up function for reference.

Move the a 'background(180);' from the setup() to the draw() function.
Like this:
function draw() {
background(180);
rectMode(RADIUS);
rect(80,300, 80, rectH);
console.log(rectH);
}

Related

Analyzing songfile using fft when i set the song volume to 0 in p5.js

In p5.js, I am using "new p5.FFT(x,y)" to analyze the amplifer mp3 file.
But this has a little problem that if you set the mp3's volume to 0(by using .setVolume(x)) the song file cannot be analyzed maybe because you set volume to 0 so there's no input.
So i want to know how to analyze songfile even when i set the volume to 0.
The trick is you need to connect your FFT at a point where the volume is still non-zero, and then have a node down stream where you control the volume. Here's an example where I've used the p5.EQ effect to control the volume of one part of the audio graph. The "Tada" sound is connected to an FFT and to the "mute" p5.EQ effect. This makes it so that the FFT visualizes the sound at full volume, but the slider controls how loud the sound actually is. The "Ding" sound on the other hand is connected directly to the output, no FFT, no volume control.
let tada, ding;
let tadaBtn, dingBtn;
let volSlider;
let fft;
let mute;
function preload() {
tada = loadSound('https://www.paulwheeler.us/files/TADA.WAV');
ding = loadSound('https://www.paulwheeler.us/files/DING.WAV');
}
function setup() {
createCanvas(windowWidth, windowHeight);
let controls = createElement('div');
controls.style('display', 'flex');
controls.position(10, 10);
tadaBtn = createButton('Tada');
tadaBtn.mouseClicked(() => {
if (!tada.isPlaying()) {
tada.play();
tadaBtn.html('Stop');
} else {
tada.stop();
}
});
tadaBtn.parent(controls);
tada.onended(() => {
tadaBtn.html('Tada');
});
dingBtn = createButton('Ding');
dingBtn.mouseClicked(() => {
if (!ding.isPlaying()) {
ding.play();
dingBtn.html('Stop');
} else {
ding.stop();
}
});
dingBtn.parent(controls);
ding.onended(() => {
dingBtn.html('Ding');
});
volSlider = createSlider(0, 1, 0, 0);
volSlider.input(() => {
mute.amp(volSlider.value());
});
volSlider.parent(controls);
tada.disconnect();
fft = new p5.FFT();
fft.setInput(tada);
mute = new p5.EQ();
mute.amp(volSlider.value());
tada.connect(mute);
mute.connect();
}
function draw() {
background(0);
drawSpectrumGraph(0, 0, width, height);
}
// Graphing code adapted from https://jankozeluh.g6.cz/index.html by Jan Koželuh
function drawSpectrumGraph(left, top, w, h) {
let spectrum = fft.analyze();
stroke('limegreen');
fill('darkgreen');
strokeWeight(1);
beginShape();
vertex(left, top + h);
for (let i = 0; i < spectrum.length; i++) {
vertex(
left + map(log(i), 0, log(spectrum.length), 0, w),
top + map(spectrum[i], 0, 255, h, 0)
);
}
vertex(left + w, top + h);
endShape(CLOSE);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>

TypeError: Cannot read property 'width' of undefined

I'm trying to write a small piece of javascript (p5.js) so I can drag in an image from my desktop into the browser. Then using my mouse cursor X&Y I can distort the image. I had this code working previously by just simply loading the image, but now I wish to have some interactive element with it so the user can load any image they want and apply the same effect.
var c;
var slice;
var sliceCount;
var sliceWidth;
var sliceWidthChecker;
function setup(){
var c = createCanvas(500,500);
background(51);
fill(255);
c.drop(gotFile);
}
function dropped() {
background(0);
}
function gotFile(file){
var img = createImg(file.data).hide();
image(img, 0, 0, 500, 500);
console.log(img)
noStroke();
stroke(0);
fill(255);
text(file.name, 20, 60);
text(file.size+' bytes', 20, 110);
}
function draw(){
/* setting the sliceWidth */
sliceWidth=windowWidth/12;
/* setting the how many slice on the canvas depending on the sliceWidth
*/
var maxSliceCount = windowWidth/sliceWidth;
/* this is trying to control, if the get pixel reads beyond the image,
it goes back, but didn't work.*/
if (sliceWidth >= c.width) {
sliceWidthChecker = sliceWidth - c.width;
} else {
sliceWidthChecker = sliceWidth;
}
/* drawing the slices */
for (sliceCount = 0; sliceCount < maxSliceCount; sliceCount++) {
/* sliceWidthChecker is trying to fix if get pixel reads beyond the
image, didn't work, can be replace by simply "sliceWidth" */
/* *0.5 after mouseX is to help if the mose move too much */
slice = c.get(mouseX*0.5+sliceCount*40, 0, sliceWidthChecker,
c.height);
image(slice, sliceWidth*sliceCount, windowHeight/10, slice.width,
slice.height);
}
}
You're declaring a variable c at the top of your sketch:
var c;
Then you're redeclaring a new variable that happens to have the same name inside your setup() function:
var c = createCanvas(500,500);
Because of the var keyword here, this is a different variable then the one you created above!
Then later, you use the original c variable:
if (sliceWidth >= c.width) {
But note that the original c variable has never been given a value. That's what's causing your error.
To fix this error you can simply remove the var keyword when you initialize the second c variable:
c = createCanvas(500,500);
This code sets the original c variable to the canvas value.
After this you have other errors in your code, but this should get you over this hurdle.

Toggle function not working in Processing (ControlP5)

I just made my image generator work with PNG files. For now, it's divided into 3 categories (backgrounds, objects & texts). These are now all combined, and with every mouse click it randomises these PNGs.
I made three toggles, where you could to choose to show either the background and the objects on top, all of them, or all separate. Whenever I run the sketch, it shows the "grey" background, but when I use the toggles, it doesn't show anything, or shows a flickering image, where the mouse-click can't be used to go to the next image. I can't seem to find the problem. Hopefully, you can help. :)
import controlP5.*;
boolean showBackground = false;
boolean showObjects = false;
boolean showGrids = false;
ControlP5 cp5;
PImage[] myImageArray = new PImage[8];
PImage[] myImageArray2 = new PImage[15];
PImage[] myImageArray3 = new PImage[15];
void setup() {
size(1436, 847);
background(211, 211, 211);
for (int i=0; i<myImageArray.length; i++) {
myImageArray[i] = loadImage ( "o" + i + ".png");
myImageArray2[i] = loadImage ( "g" + i + ".png");
myImageArray3[i] = loadImage( "b" + i + ".jpg");
cp5 = new ControlP5(this);
// create a toggle and change the default look to a (on/off) switch look
cp5.addToggle("showBackground")
.setPosition(40, 250)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showObjects")
.setPosition(40, 400)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showGrid")
.setPosition(40, 600)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
}
display();
}
void display() {
image(myImageArray3[(int)random(myImageArray.length)], 0, 0, 1436, 847); // b
image(myImageArray2[(int)random(myImageArray.length)], 0, 0, 1436, 847); // g
image(myImageArray[(int)random(myImageArray.length)], 0, 0, 1436, 847); // o
}
void mousePressed() {
display();
}
void draw() {
pushMatrix();
if (showBackground==false) {
image(myImageArray3[(int)random(myImageArray.length)], 0, 0, 1436, 847); // b
} else {
background(211, 211, 211);
}
if (showGrids==false) {
image(myImageArray2[(int)random(myImageArray.length)], 0, 0, 1436, 847); // g
} else {
background(211, 211, 211);
}
if (showObjects==false) {
image(myImageArray[(int)random(myImageArray.length)], 0, 0, 1436, 847); // o
} else {
background(211, 211, 211);
}
popMatrix();
}
Here are a couple of things where the logic your wrote in your code might not match what you had in mind:
When you call display() on mouse it renders those 3 images once (also it will be different images within those since it's using a randomised index). Similarly in draw(), when an does get picked to be rendered, frames will be flickering fast as a random index is generated multiple times per second(each frame). You may want to randomise indices in a different event (e.g. mouse or key press) and store this value in a variable you can re-use.
the conditions you use in draw(): you probably meant to check if the values are true(toggled enabled/turned on in controlP5) ? (e.g. e.g. if (showBackground==true) and initialise the toggles with false, instead of true?)
a big one: in draw() , after each condition(showBackground,showGrids,showObjects), if it's false, you're clearing the the whole frame (so a previous image would be erased)
you have 3 arrays, but you use the size of the first(myImageArray.length) only, which means, even though you may have more images for myImageArray2 and myImageArray3, you're not loading, nor displaying them.
The third grid is labeled "showGrid" when it should be "showGrids": if you aren't consistent with the toggle labels and variable names, toggles won't update the variable names.
you should use more descriptive names for the arrays: it will make it easier to scan/follow your code on the long run.
there's no need to add toggles multiple times in the for loop where you load images: once will do.
Here's what I mean:
import controlP5.*;
boolean showBackground = false;
boolean showObjects = false;
boolean showGrids = false;
ControlP5 cp5;
PImage[] objects = new PImage[8];
PImage[] grids = new PImage[15];
PImage[] backgrounds = new PImage[15];
int currentImage = 0;
void setup() {
size(1436, 847);
//load objects
for (int i=0; i<objects.length; i++) {
objects[i] = loadImage ( "o" + i + ".png");
}
//load grids
for(int i = 0 ; i < grids.length; i++){
grids[i] = loadImage ( "g" + i + ".png");
}
//load backgrounds
for(int i = 0 ; i < grids.length; i++){
backgrounds[i] = loadImage( "b" + i + ".jpg");
}
//setup UI
cp5 = new ControlP5(this);
// create a toggle and change the default look to a (on/off) switch look
cp5.addToggle("showBackground")
.setPosition(40, 250)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showObjects")
.setPosition(40, 400)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showGrids")
.setPosition(40, 600)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
}
void mousePressed() {
//go to next image index
currentImage = currentImage + 1;
//check if the incremented index is still valid, otherwise, reset it to 0 (so it doesn't go out of bounds)
if (currentImage >= objects.length) {
currentImage = 0;
}
}
void draw() {
//clear current frame
background(211);//for gray scale value you can just use one value: the brightness level :)
if (showBackground==true) {
image(backgrounds[currentImage], 0, 0, 1436, 847); // b
}
if (showGrids==true) {
image(grids[currentImage], 0, 0, 1436, 847); // g
}
if (showObjects==true) {
image(objects[currentImage], 0, 0, 1436, 847); // o
}
}
Note that currently the same index is used for all 3 arrays.
You may want to add a separate index variable for each array (e.g. currentObjectIndex, currentBackgroundIndex, currentGridIndex) that you can increment independently of each other.
I recommend having a bit more patience and double checking your code first.
Visualise what each line of code will do, then check if it actually does what you expect it to do. Either you will learn something new or improve your logic.
Also, if mentally joggling 3 arrays is tricky (and it can be), go one step back: try your logic with one array only until you get the hang of it, then move on.
A step backwards is sometimes a step forward when you're going in the wrong direction.
As creative as you'd like to be with Processing, bare in mind, the interface to plugging your ideas to it is still a series of one instruction at a time, each precisely tuned to do exactly what you want it to do. There's room for fun, but unfortunately you need to get past the boring parts first

Disable brush resize (DC.js, D3.js)

Brush extent needs to be changed only from a dropdown as shown here: https://jsfiddle.net/dani2011/67jopfj8/3/
Need to disable brush extending by:
1) Extending an existing brush using the handles/resize-area of the brush
Gray circles are the handels:
2) Dragging a new brush by clicking on the brush background, where the
haircross cursor appears.
JavaScript file
Removed the handles of the brush:
timeSlider.on('preRedraw',function (chart)
{
var timesliderSVG = d3.select("#bitrate-timeSlider-chart").selectAll("g.brush").selectAll("g.resize").selectAll("*").data(data[0]).exit().remove();})
If using css instead:
#bitrate-timeSlider-chart g.resize {
display:none;
visibility:hidden;
Now it looks like this:
.
The rect and the path elements inside "resize e","resize w" were removed:
However,the "resize e", "resize w" for extanding the brush still exist:
g.resize.e and g.resize.w dimesions are 0*0:
Furthurmore,after deleting "resize e","resize w" in the "developer tools/elements" in chrome,they reappear after moving the brush.
Tried to remove the resize-area in brushstart,brush,brushend:
timeSlider.on('renderlet', function (chart) {
var brushg = d3.select("#bitrate-timeSlider-chart").selectAll("g.brush");
var resizeg = brushg.selectAll("g.resize").selectAll("*").data(data[0]);
var timesliderSVG4 =
brushg.on("brushstart", function () {resizeg.exit().remove()}).on("brush", function () { resizeg.exit().remove() }).on("brushend", function () {resizeg.exit().remove() })
dc.js file
Tried to change setHandlePaths,resizeHandlePath:
1)
Remarked the _chart.setHandlePaths(gBrush):
_chart.renderBrush = function (g) {....
// _chart.setHandlePaths(gBrush);
...}
2) Changed _chart.setHandlePaths = function (gBrush) for example by removing the gbrush.path:
// gBrush.selectAll('.resize path').exit().remove();
3) Remarked/changed _chart.resizeHandlePath = function (d) {...}.
d3.js file
1) Remarked/changed resize such as:
mode: "move" //mode: "resize" ,
var resize = g.selectAll(".resize").data(resizes[0], d3_identity);
Using resizes[0] disable the brush rendering on the background but still can re-extend the existing brush
2) Remarked/changed d3_svg_brushResizes
3) In d3.svg.brush = function():
a) Added .style("display","none"):
background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("display", "none").style("cursor", "none");
b) background.exit().remove();
The cursor now is "pointer" instead of "haircross" extending the brush to a full width
c) d3_svg_brushCursor disabled makes the whole brush disappear
4) Changed the pointer-events as specified here: https://developer.mozilla.org/en/docs/Web/CSS/pointer-events
5) console.log in different places to track the different brush events:
function d3_event_dragSuppress(node) {
console.log("here2 ");
}
if (d3_event_dragSelect) {
console.log("here3 d3_event_dragSelect");
...
}
return function (suppressClick) {
console.log("suppressClick1");
...
var off = function () {
console.log("suppressClick2");
...
w.on(click, function () {
console.log("suppressClick3")
...
function d3_mousePoint(container, e) {
console.log("d3_mousePoint1")
...
if (svg.createSVGPoint) {
console.log("createSVGPoint");
...
if (window.scrollX || window.scrollY) {
console.log("createSVGPoint1");
svg = d3.select("body").append("svg").style({
...
function dragstart(id, position, subject, move, end) {
console.log("dragstart")
...
function moved() {
console.log("moved ");
console.log("transition1");
...
if (d3.event.changedTouches) {
console.log("brushstart1");
...
} else {
console.log("brushstart2");
..
if (dragging) {
console.log("dragging4");
....
if (d3.event.keyCode == 32) {
if (!dragging) {
console.log("notdragging1");
...
function brushmove() {
console.log("brushmove");
...
if (!dragging) {
console.log("brushmove!dragging");
if (d3.event.altKey) {
console.log("brushmove!dragging1");
...
if (resizingX && move1(point, x, 0)) {
console.log("resizeXMove1");
...
if (resizingY && move1(point, y, 1)) {
console.log("resizeYMove1");
...
if (moved) {
console.log("moved");
...
}
function move1(point, scale, i) {
if (dragging) {
console.log("dragging1");
...
if (dragging) {
console.log("dragging2");
...
} else {
console.log("dragging10");
...
if (extent[0] != min || extent[1] != max) {
console.log("dragging11");
if (i) console.log("dragging12"); yExtentDomain = null;
console.log("dragging13");
function brushend() {
console.log("brushend");
...
The two changes that seemed to get closest to the needed result are in d3.js:
1) Using resizes[0] disables the brush rendering on the background but still can re-extend the existing brush
var resize = g.selectAll(".resize").data(resizes[0], d3_identity);
2) Removing the brush's background changes the cursor to "pointer" instead of "haircross",extending the brush to a full width only when clicking on the graph
`background.exit().remove();`
Any help would be very appreciated!
This is from the accepted answer in Disable d3 brush resize, as suggested by #altocumulus. I didn't see a response from #Dani on this idea in particular, but thought I'd go ahead and try it, since I've seen other people try it in the past. (Probably on the dc.js users group.)
It looks a little twitchy, because d3.js will draw the brush at the new extent, and then a moment later we reset the extent to what we want, but functionally it seems to do what we want.
In dc.js the function that handles brush "rounding" is coordinateGridMixin.extendBrush:
_chart.extendBrush = function () {
var extent = _brush.extent();
if (_chart.round()) {
extent[0] = extent.map(_chart.round())[0];
extent[1] = extent.map(_chart.round())[1];
_g.select('.brush')
.call(_brush.extent(extent));
}
return extent;
};
Notice that it's following the same pattern as Lars' answer. Well, this is sort of like rounding, right? Let's override it.
First, let's store the current number of hours when it's set through the dropdown:
var graphSpan;
function addHours(amountHours) {
graphSpan = amountHours;
// ...
Next let's override coordinateGridMixin.extendBrush:
timeSlider.extendBrush = function() {
var extent = timeSlider.brush().extent();
if(graphSpan) {
extent[1] = moment(extent[0]).add(graphSpan, 'hours');
}
return extent;
}
We just replace the function. If we needed to reuse the old implementation in our function, we could use dc.override.
If graphSpan has been set, we add that amount to the beginning to get the end. If it's not set, we allow the user to specify the brush width - you'd need to default the graphSpan and the select widget if you wanted that part to work automatically.
Well, let's admit it: it's very twitchy, but it works:
EDIT: Got rid of the twitch! The problem was that dc.js was setting the brush extent asynchronously after a little while (in response to the filter event). If we also set it during extentBrush then it never shows the wrong extent:
timeSlider.extendBrush = function() {
var extent = timeSlider.brush().extent();
if(graphSpan) {
extent[1] = moment(extent[0]).add(graphSpan, 'hours');
timeSlider.brush().extent(extent);
}
return extent;
}
https://jsfiddle.net/gordonwoodhull/xdo05chk/1/
What worked for me:
in d3:
disable resize handles
d3.selectAll('.brush>.handle').remove();
disable crosshair
d3.selectAll('.brush>.overlay').remove();
or
in css:
disable resize handles -
.handle {
pointer-events: none;
}
disable crosshair -
.overlay {
pointer-events: none;
}

List of Scrollable Checkboxes in Processing

I am a newbie to programming GUIs and Processing. My questions is how can I get a list of checkboxes that I can scroll through? What I want is exactly the list of countries on the right here (http://goo.gl/MIKHi4).
I looked through the ControlP5 library and was able to find Checkboxes, but I don't know how I can make a scrollable list of them.
Thank you.
I had also been searching for this last week and hoping that there was a ready-for-use library for me to easily add the scrollable checkboxes to my application, but finally I had no luck. At last, what I did was implementing my own scrollable list of checkboxes.
Firstly, I added a ControlP5 slider as the scroll bar, and then at each frame, got value from the slider and draw the specific checkboxes based on that value.
Suppose that you have a list of 200 countries for the user to select. Then the code will be like:
ControlP5 cp5;
Slider scrollBar;
PFont fLabel;
int boxOver = -1; //Indicate mouse is over which checkbox
boolean[] boxSelected; //Checkbox selected or not
void setup() {
size(1024, 800);
colorMode(HSB, 360, 100, 100);
cp5 = new ControlP5();
scrollbar = cp5.addSlider("scrollbar")
.setPosition(1005, 110)
.setRange(0, 180)
.setSize(15, 490)
.setHandleSize(30)
.setSliderMode(Slider.FLEXIBLE)
.setValue(180); //Put handler at top because 0 value is at bottom of slider
fLabel = createFont("Arial", 12, false);
boxSelected = new boolean[200];
for(int i=0;i<200;i++) {
boxSelected[i] = false;
}
}
void draw() {
noFill();
stroke(200, 255);
rect(820, 110, 200, 490); //The outline of the scrollable box
stroke(150, 255);
int count = 0;
//Suppose that you want to display 20 items each time
for(int i=180-(int)scrollBar.getValue();i<180-(int)scrollBar.getValue()+20;i++) {
if(boxOver < 0) {
if(mouseX>=825 && mouseX<837 && mouseY >= 120+count*24 && mouseY <= 132+count*24) {
boxOver = i;
cursor(HAND);
}
}
if(boxSelected[i]) {
fill(50); //If the box is selected, fill this box
} else {
fill(360);
}
rect(825, 120+count*24, 12, 12); //Draw the box
//Draw the label text
textFont(fLabel);
fill(50);
text(countries[i], 843, 132+count*24); //Suppose the country names are stored in countries[]
count++;
}
}
void mousePressed() {
if(boxOver >=0) {
boxSelected[boxOver] = !boxSelected[boxOver]; //Toggle selection
}
}
Hope this helps you, or anyone who may encounter the same problem in the future.
There is now an example in the experimental examples called ControlP5SliderList

Resources