Animated rectangle used as an audio playhead isn't displaying properly - p5.js

I'm building a drum machine following a tutorial on Youtube, and I'm trying to use a rectangle as a playhead, which is animating in time with the beat.
The "Space" key activates the drum loop. The variable 'sPat' (sequence pattern), defined as an array [1, 2, 3... etc.] shows where the rectangle should be drawn. beatIndex represents the position taken from that array.
It seems to be working except the rectangle doesn't display most of the time, it's blinking and it is only visible about 5% of the time, randomly. I tried playing around with the rectangle parameters to see what is happenning, setting it to a fixed position and tried Firefox and Chrome. I'm not at all familiar with p5 yet.
What I need help with is:
why isn't the rectangle displaying?
why are the dots becomming colored in red as soon as I hit Space and how to prevent this?
I tried to minimize the code to a smaller working version. The 'sequence' function drawing the rectangle is at the end. You can also run it here: https://editor.p5js.org/dmihaescu/sketches/OFZm52gmF
let dum, dPat, dPhrase, drums;
let beatLength;
let sPat;
var canvas;
var cellWidth;
let beatIndex;
function setup() {
canvas = createCanvas(600, 100);
beatLength = 16;
cellWidth = width/beatLength;
beatIndex = 0;
dum = loadSound('assets/dum.flac', () => {});
dPat = [1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0];
sPat = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16];
drums = new p5.Part();
drums.setBPM('45')
drums.addPhrase('dum', (time) => {dum.play(time)}, dPat);
drums.addPhrase('seq', sequence, sPat);
}
function keyPressed() {
if(key === " ") {
if(dum.isLoaded()) {
if(!drums.isPlaying) {
drums.loop();
} else {
drums.stop();
}
} else {
console.log("not yet loaded");
}
}
}
function draw() {
background(0);
stroke("gray");
strokeWeight(2);
for (let i = 0; i < beatLength+1; i++) {
line(i*width/beatLength, 0, i*width/beatLength, height);
}
for (let i = 0; i < 4; i++) {
line(0, i*height/3, width, i*height/3);
}
noStroke();
for (let i = 0; i < beatLength; i++) {
if(dPat[i] === 1) {
ellipse(i*width/beatLength+0.5*width/beatLength,height/2,10);
}
}
}
function sequence (time, beatIndex) {
stroke('red');
fill(255,0,0,50);
rect((beatIndex-1)*cellWidth, 0, 50, height);
}

No need for a seperate function just put the below code in draw function :-
stroke('red');
fill(255,0,0,50);
rect((beatIndex-1)*cellWidth, 0, 50, height);
Try and tell whether it worked out or not

Related

Not able to copy paste a section of image in P5.js

I have been working on a project in which I have to select an area of image and then use CTRL+C and CTRL+V to copy and paste the selected area. I have tried creating a buffer using createGraphics() but did not work. I have also tried getting pixels one by one and creating image but that is not working either. Can somebody help? Below is my code.
if (this.isCtrlPressed == true && p5.key == 'v') {
let img = p5.createImage(this.dw, this.dh);
p5.draw = () => {
img.loadPixels();
for (let i = 0; i < parseInt(this.sw); i++) {
for (let j = 0; j < parseInt(this.sh); j++) {
img.set(i, j, p5.get(i, j));
}
}
img.updatePixels();
p5.stroke(255);
p5.image(img, 10, 10);
p5.noFill();
p5.rect(p5.mouseX, p5.mouseY, 10, 10);
return;
}
You're not too far off.
Bare in mind that you can also call get() with width, height arguments, not just the first two (x, y): this will make it easy to copy a section of the image.
The p5.Graphics you get using createGraphics() is handy indeed.
Remember to store the selection rectangle properties (x, y, width, height) to copy the image in the first place.
Here's a very rough sketch based on the CreateImage p5.js example:
/*
* #name Create Image
* #description The createImage() function provides a fresh buffer of pixels to
* play with. This example creates an image gradient.
*/
let img; // Declare variable 'img'.
// copy/paste selection rectangle
let selection = {x:0, y:0, w:0, h:0};
// p5.Image from main image using selection
let clipboard;
// where to paste the clipboard p5.Image
let pasteBuffer;
function setup() {
createCanvas(720, 400);
img = createImage(230, 230);
img.loadPixels();
for (let x = 0; x < img.width; x++) {
for (let y = 0; y < img.height; y++) {
let a = map(y, 0, img.height, 255, 0);
img.set(x, y, [0, 153, 204, a]);
}
}
img.updatePixels();
// selection drawing style
noFill();
stroke(255);
// setup buffer to paste into
pasteBuffer = createGraphics(width, height);
}
function draw() {
background(0);
// render original image
image(img, 0, 0);
// render pasted graphics
image(pasteBuffer, 0, 0);
// render selection rectangle
rect(selection.x, selection.y, selection.w, selection.h);
}
function mousePressed(){
// store selection start
selection.x = mouseX;
selection.y = mouseY;
}
function mouseDragged(){
// update selection dimension as the difference between the current mouse coordinates and the previous ones (selection x, y)
selection.w = mouseX - selection.x;
selection.h = mouseY - selection.y;
}
function keyPressed(keyEvent){
if (key == 'c') {
console.log("copy image", selection);
// use get() to "clone" a subsection of the main image
clipboard = img.get(selection.x, selection.y, selection.w, selection.h);
}
if (key == 'v') {
console.log("paste image");
if(clipboard){
// simply render the clipboard image
pasteBuffer.image(clipboard, mouseX, mouseY);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
Note the above isn't perfect (e.g. you need to use abs() to handle making selections from right to left, not just left to right), but hopefully it illustrates the usage of a selection rectangle, copying pixels and "pasting"/rendering them into a buffer.
Speaking of copying pixels, an alternative option could be using a p5.Image (using createImage() instead of createGraphics()) and copy() (instead of buffer.image()) (and you'd use the selection rectangle properties to know from where to where to paste pixels).

P5.JS - Filling a shape with a preloaded image, using an object, its properties and its functions

I am trying to create an object and for it to have a shape (an ellipse), an image, and a function that somehow fills the shape with a pre-loaded image.
I already found this post, but I can't get it to work if I try to make it neat and fit it all into an object.
To be clear, this is what it would look like:
let image;
let hypotheticalObject
function preload()
{
image = loadImage('Assets/randomimage.jpeg');
}
function setup()
{
hypotheticalObject = {
objectImage: image,
graphicsBuffer: createGraphics(100, 100),
xPos: width / 2,
yPos: height / 2,
size: 50,
colour: color(255, 255, 255),
shape: function()
{
fill(this.colour),
ellipse(this.xPos, this.yPos, this.size);
},
mask: function()
{
this.graphicsBuffer.shape();
},
render: function()
{
something something
}
}
function draw()
{
hypotheticalObject.render();
}
That's kind of how far I can get, as I can't figure out how to proceed.
There are a couple of issues with your code, namely this:
shape: function() {
// ...
},
mask: function() {
this.graphicsBuffer.shape();
},
You declare shape() on hypotheticalObject but then you try to call it on graphicsBuffer? This isn't how javascript works. You could do something like this:
hypotheticalObject.graphicsBuffer.shape = function() {
// When graphicsBuffer.shape() is called, the this keyword will be bound to graphicsBuffer
// Unfortunately we need a to access members from hypotheticalObject as well, so that is inconvenient:
this.fill(hypotheticalObject.colour),
this.ellipse(hypotheticalObject.xPos, hypotheticalObject.yPos, hypotheticalObject.size);
}
However I don't think that is the most idiomatic approach either. Here's a working solution that tries to stay close to your code:
let img;
let hypotheticalObject;
function preload() {
img = loadImage('https://www.paulwheeler.us/files/windows-95-desktop-background.jpg');
}
function setup() {
createCanvas(windowWidth, windowHeight);
hypotheticalObject = {
objectImage: img,
// The graphics you're using for the mask need to be the size of the image
graphicsBuffer: createGraphics(img.width, img.height),
xPos: width / 2,
yPos: height / 2,
size: 50,
init: function() {
this.maskedImage = createImage(this.objectImage.width, this.objectImage.height);
},
updateShape: function() {
this.graphicsBuffer.clear();
// The fill color is irrelevant so long as it is opaque
this.graphicsBuffer.fill(0);
// These drawing instructions need to happen on the p5.Graphics object
this.graphicsBuffer.ellipse(this.xPos, this.yPos, this.size);
},
render: function() {
this.updateShape();
// When you call mask on an image it changes the image so we need to make a copy
this.maskedImage.copy(
this.objectImage,
0, 0, this.objectImage.width, this.objectImage.height,
0, 0, this.objectImage.width, this.objectImage.height
);
this.maskedImage.mask(this.graphicsBuffer);
// Always draw the image at 0, 0 since the masked portion is already offset
image(this.maskedImage, 0, 0);
}
}
hypotheticalObject.init();
}
let xv = 2;
let yv = 2;
function draw() {
background(255);
hypotheticalObject.xPos += xv;
hypotheticalObject.yPos += yv;
if (hypotheticalObject.xPos > width) {
hypotheticalObject.xPos = width;
xv *= -1;
} else if (hypotheticalObject.xPos < 0) {
hypotheticalObject.xPos = 0;
xv *= -1;
}
if (hypotheticalObject.yPos > height) {
hypotheticalObject.yPos = height;
yv *= -1;
} else if (hypotheticalObject.yPos < 0) {
hypotheticalObject.yPos = 0;
yv *= -1;
}
hypotheticalObject.render();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Update: Animated Gifs
If you want this to work with animated gifs, you have to deal with an idiosyncrasy of p5.js: the fact that it updates gif images only when they are rendered with either the image() function or the texture() function. And that rendering code depends on the drawing context having timing information, and therefor does not work with off screen p5.Graphics buffers. Here's an example:
let img;
let hypotheticalObject;
function preload() {
img = loadImage('https://www.paulwheeler.us/files/Clouds.gif');
}
function setup() {
createCanvas(img.width, img.height);
hypotheticalObject = {
objectImage: img,
// The graphics you're using for the mask need to be the size of the image
graphicsBuffer: createGraphics(img.width, img.height),
xPos: width / 2,
yPos: height / 2,
size: 100,
init: function() {
this.maskedImage = createImage(this.objectImage.width, this.objectImage.height);
},
updateShape: function() {
this.graphicsBuffer.clear();
// The fill color is irrelevant so long as it is opaque
this.graphicsBuffer.fill(0);
// These drawing instructions need to happen on the p5.Graphics object
this.graphicsBuffer.ellipse(this.xPos, this.yPos, this.size);
},
render: function() {
this.updateShape();
// When you call mask on an image it changes the image so we need to make a copy
this.maskedImage.copy(
this.objectImage,
0, 0, this.objectImage.width, this.objectImage.height,
0, 0, this.objectImage.width, this.objectImage.height
);
this.maskedImage.mask(this.graphicsBuffer);
// Always draw the image at 0, 0 since the masked portion is already offset
image(this.maskedImage, 0, 0);
}
}
hypotheticalObject.init();
}
let xv = 2;
let yv = 2;
function draw() {
// In order to get a gif to animate it needs to be passed to either the image() function or the texture() function. And unfortunately it must be the global one, not the version on an off-screen p5.Graphics
// However we don't actually want to display this
image(img, width, height);
background(255);
hypotheticalObject.xPos += xv;
hypotheticalObject.yPos += yv;
if (hypotheticalObject.xPos > width) {
hypotheticalObject.xPos = width;
xv *= -1;
} else if (hypotheticalObject.xPos < 0) {
hypotheticalObject.xPos = 0;
xv *= -1;
}
if (hypotheticalObject.yPos > height) {
hypotheticalObject.yPos = height;
yv *= -1;
} else if (hypotheticalObject.yPos < 0) {
hypotheticalObject.yPos = 0;
yv *= -1;
}
hypotheticalObject.render();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

How do I make a object move towards the mouse in p5.js?

I'm making a zombie game in p5js, does anyone have any recommendations on to make a shape or object move towards the mouse(this will be used to make the gun)? and if anyone knows how to make more enemies spawn more often each round that would be nice to!:)
Sorry if the solution is super simple, I still kinda new!
code:
let dogmode;
let round;
let zombie, player;
let x, y;
let health, speed, damage, playerhealth;
function setup() {
dogmode=false;
createCanvas(400,400);
round = 1;
bullet ={
damage:20
};
//Player and zombie parameters
zombie = {
pos: createVector(500, 200),
//increase by 100 each round until round 9
health: 150,
speed: 1,
};
player = {
pos: createVector(200, 200),
//default
health: 100,
};
fill(0);
}
function draw() {
if( dogmode===true){
player.health=player.health+100;
}
background(220);
stroke(0);
line(player.pos.x,player.pos.y,mouseX,mouseY);
fill('gray')
ellipse(mouseX,mouseY,10,10);
push();
//player
fill("green");
ellipse(player.pos.x, player.pos.y, 20, 20);
//HUD or something
fill("black");
text("" + player.health, 10, 10);
//zombie(s)
fill("gray");
rect(zombie.pos.x, zombie.pos.y, 20, 20);
//Damage system
if (p5.Vector.sub(player.pos, zombie.pos).mag() <= 30) {
player.health = player.health - 1;
}
//Health detection
if (player.health <= 0) {
background(0);
textSize(20);
text("You died \n Round " + round, 165, 200);
}
if (keyIsDown(87)) {
player.pos.y -= 2;
}
if (keyIsDown(83)) {
player.pos.y += 2;
}
if (keyIsDown(65)) {
player.pos.x -= 2;
}
if (keyIsDown(68)) {
player.pos.x += 2;
}
zombie.pos.add(p5.Vector.sub(player.pos, zombie.pos).limit(zombie.speed))
pop();
//Create bullet
if(mouseIsPressed ){
fill('yellow')
ellipse(mouseX,mouseY,5,5);
//Shoots bullet
}
}
In order to fire a bullet you need to:
Determine the direction of motion at the time the bullet is fired.
Save that vector so that the direction remains constant for the life of the bullet.
Update the position of the bullet each frame after it is fired.
Determining the direction of motion is simple vector arithmetic if you have two vectors, A and B, and subtract A - B you will get a vector representing the offset from B to A (i.e. pointing in the direction of A from B):
It is important to save this direction vector, because as the player, mouse, and bullet move the result of this computation will continually change; and bullets don't usually change direction once fired.
const BULLET_SPEED = 5;
let player;
let bullet;
function setup() {
createCanvas(windowWidth, windowHeight);
bullet = {
damage: 20,
};
player = {
pos: createVector(width / 2, height / 2),
//default
health: 100,
};
}
function draw() {
background(220);
stroke(0);
// By taking the difference between the vector pointing to the mouse and the
// vector pointing to the player we get a vector from the player to the mouse.
let mouseDir = createVector(mouseX, mouseY).sub(player.pos);
// Limit the length to display an indicator of the bullet direction
mouseDir.setMag(30);
// Because we want to draw a line to the point mouseDir offset away from
// player, we'll need to add mouseDir and player pos. Do so using the static
// function so that we don't modify either one.
let dirOffset = p5.Vector.add(player.pos, mouseDir);
// direction indicator
line(player.pos.x, player.pos.y, dirOffset.x, dirOffset.y);
fill("gray");
ellipse(dirOffset.x, dirOffset.y, 10, 10);
//player
fill("green");
ellipse(player.pos.x, player.pos.y, 20, 20);
if (keyIsDown(87)) {
player.pos.y -= 2;
}
if (keyIsDown(83)) {
player.pos.y += 2;
}
if (keyIsDown(65)) {
player.pos.x -= 2;
}
if (keyIsDown(68)) {
player.pos.x += 2;
}
//Create bullet
if (mouseIsPressed) {
// Notice there can be only one bullet on the screen at a time, but there's
// no reason you couldn't create multiple bullets and add them to an array.
// However, if you did, you might want to create bullets in a mouseClicked()
// function instead of in draw() lets you create too many bullets at a time.
bullet.firing = true;
// Save the direction of the mouse;
bullet.dir = mouseDir;
// Adjust the bullet speed
bullet.dir.setMag(BULLET_SPEED);
// Position the bullet at/near the player
bullet.initialPos = dirOffset;
// Make a copy so that initialPos stays fixed when pos gets updated
bullet.pos = bullet.initialPos.copy();
//Shoots bullet
}
if (bullet.firing) {
fill('yellow');
ellipse(bullet.pos.x, bullet.pos.y, 5, 5);
// move the bullet
bullet.pos.add(bullet.dir);
if (bullet.pos.dist(bullet.initialPos) > 500) {
bullet.firing = false;
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

p5.js Add a dissapearing ellipse trail to Lissajous curve line

I have a simple code that traces the Liss cruve with a small ellipse. I was wondering how to add a fading trail to this shape so it represents the cruve more clearly. I only know a bit about adding trails that follows the mouse but I'm not sure how to do this one.
Any help is appreciated, here is the code:
var t = 0;
function setup() {
createCanvas(500, 500);
fill(255);
}
function draw() {
background(0);
for (i = 0; i < 1; i++) {
y = 160*sin(3*t+PI/2);
x = 160*sin(1*t);
fill(255);
ellipse(width/2+x, height/2+y, 5, 5);
t += .01;
}
}
Try changing background(0) to background(0, 0, 0, 4) :)
Here is a working example:
https://editor.p5js.org/chen-ni/sketches/I-FbLFDXi
Edit:
Here is another solution that doesn't use the background trick:
https://editor.p5js.org/chen-ni/sketches/HiT4Ycd5U
Basically, it keeps track of each point's position and redraws them in every frame with updated alpha to create the "fading out" effect.
var t = 0;
var particleArray = [];
function setup() {
createCanvas(500, 500);
}
function draw() {
background(0);
y = width / 2 + 160 * sin(3 * t + PI / 2);
x = height / 2 + 160 * sin(1 * t);
particleArray.push(new Particle(x, y, t));
for (i=0; i<particleArray.length; i++) {
particleArray[i].show(t);
}
//keep the array short, otherwise it runs very slow
if (particleArray.length > 800) {
particleArray.shift();
}
t += .01;
}
function Particle(x, y, t) {
this.x = x;
this.y = y;
this.t = t;
this.show = function(currentT) {
var _ratio = t / currentT;
_alpha = map(_ratio, 0, 1, 0, 255); //points will fade out as time elaps
fill(255, 255, 255, _alpha);
ellipse(x, y, 5, 5);
}
}

Moving an objects while others stay static in a click controlled sketch

So I'm trying to build an animation that I can step through with mouse clicks. Adding individual objects click by click is easy. Sequence I want is as follows:
One object(a) drawn initially.
First mouse click adds an object(b).
Second mouse click adds an object(c).
Third mouse click, object(c) should move across the screen and disappear.
I'm having a problem on the last part of the sequence. I can't figure out how to make the object move and still maintain the static part of the sketch. The normal way of doing movement is to change the coordinates of the object with each loop through the draw() function, and use the background to cover up the previous objects. Can't do that in this case because I need object(a) and object(b) to be persistent.
Code below. Thanks for your help!
var count = 0;
function setup() {
createCanvas(200, 200);
a = new Object1(20, 40);
b = new Object1(20, 85);
c = new Object1(20, 130);
}
function draw() {
background(200);
a.display();
if (count == 1) {
b.display();
}
if (count == 2) {
b.display();
c.display();
}
if (count == 3) { //this is where I have a problem
}
if (count > 3) {
count = 0;
}
}
function Object1(ix, iy, itext) {
this.x = ix;
this.y = iy;
this.text = itext;
this.display = function() {
fill(160);
rect(this.x, this.y, 40, 40);
}
}
function mousePressed() {
count++;
}
Generally how you'd do this is by drawing the static part of your scene to an off-screen buffer, which you can create using the createGraphics() function. From the reference:
var pg;
function setup() {
createCanvas(100, 100);
pg = createGraphics(100, 100);
}
function draw() {
background(200);
pg.background(100);
pg.noStroke();
pg.ellipse(pg.width/2, pg.height/2, 50, 50);
image(pg, 50, 50);
image(pg, 0, 0, 50, 50);
}
You'd draw the static part to the buffer, then draw the buffer to the screen, then draw the dynamic stuff on top of it each frame.
This has been discussed before, so I'd recommend doing a search for stuff like "processing pgraphics" and "p5.js buffer" to find a bunch more information.

Resources