Define a agent class in supercollider

I have a question about defining a class in supercollider.
I would like to call a multiple agent in a supecollider, and add some function later.
for now I need two things to start,

  1. making agent class, and
  2. making a method to update and adding multiple agent class

This is my first try

Define a class

Agent {
    var x, y, vx, vy;
    
    // Constructor function
    *new { arg x, y, vx, vy;
        ^super.new.init(x, y, vx, vy);
    }
    
    // Initialization function
    init { arg x, y, vx, vy;
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
        ^this;
    }
    
    // Update function
    update { arg dt;
        x = x + vx * dt;
        y = y + vy * dt;
    }
}

Call a class is scd file

// Create a collection of agents
(
~agents = (1..10).collect { Agent.new(rrand(0, 1), rrand(0, 1), rrand(-0.1, 0.1), rrand(-0.1, 0.1)) };
)

// Update the agents' positions over time
(
inf.do {
    ~agents.do { arg agent;
        agent.update(0.1);
    };
    0.1.wait;
};
)

but the post message posted doesn’t understand the “x_”

Does anyone have some ideas to solve?

Right, it’s undefined in your example.

See Writing Classes | SuperCollider 3.12.2 Help (this links to the Getters and Setters section).

hjh

There are no public members in supercollider. Instead you need to provide a function that can get and set the value.

It isn’t quite obvious, but by using `this’ inside the init function you are accessing the object as if you were outside of it.

Also, its often better to use newCopyArgs here, likewise default values are a must because if the user doesn’t provide a value everything will break (because it will be nil)

Agent {
    var x, y, vx, vy;

	*new { | x=0, y=0, vx=0, vy=0 |
        ^super.newCopyArgs(x, y, vx, vy);
    }

    update { | dt |
        x = x + vx * dt;
        y = y + vy * dt;
    }
}

Now there is no way to get the members out of the object.
To do that, you need to add this…

var <x, <y, vx, vy;

now x and y can be accessed outside, e.g., a.x

Also, consider using Points…

Agent {
    var <position, velocity;
	*new { | position, velocity |
		^super.newCopyArgs(position ? Point(), velocity ? Point());
    }
    update { | dt | position = position + velocity * dt; }
}
1 Like

For now, I can check the values that are what I want!
and I have a few more questions

  1. Then If I define a 10 agent, How to I check each agent’s x and y values?
  2. my Idea is to define several agents in space and make agents move with specific algorithms, my main goal is to use that data for the sound parameter, but for now, I would like to check each step with visuals do you have some ideas about implementation?

I already build this kind of system in Touchdesigner, but quite hard to define in a supercollider haha

You can easily create an array containing many instances of any object (see Array.fill). Then you use array indexing (at method, or bracket notation like in C/Java) to access one of the instances, and once you have one of the instances, there is no difference in the way you get x and y.

A single agent instance is responsible only for itself. It isn’t responsible for any collection behavior at all – collection behavior is the responsibility of a collection object. Keep a strict division of labor to avoid confusion.

See UserView.

After one cycle of updating the agents’ positions (recommend to do all of them at once), you’d call .refresh on your UserView. This will call the UserView’s drawFunc function. Here, you would iterate over all the agents and plot a shape based on pixel coordinates.

hjh

1 Like

There is absolutely no reason to use a class in this case. Class are only really useful if you have very large objects with lots of behaviour, or you want to build something re-useable.

This does everything you have described.

(
var data_bounds = Rect.newSides(left: -10, top: -10, right: 10, bottom: 10);
var mk_agent = { |pos, vel| (\pos: pos ? Point(), \vel: vel ? Point()) };

var update_agent = {|a, dt=0.01, coords|
	mk_agent.(
		pos: ( a.pos + (a.vel * dt) ).wrap(coords.leftTop, coords.rightBottom), 
		vel: a.vel
	)
};

var agents = 200.collect({ 
	mk_agent.(
		pos: Point(
			data_bounds.left + data_bounds.extent.x.rand2, 
			data_bounds.top + data_bounds.extent.x.rand2
		),
		vel: Point( 5.0.rand2, 5.0.rand2)	
	) 
});

var window = Window.new("agent display");

var agent_routine = Routine({
	var delta_t = 0.01;
	loop { 
		agents = agents.collect( update_agent.(_, dt: delta_t, coords: data_bounds) );
		delta_t.wait;
	}
});

var window_routine = Routine({
	window.view.background_(Color.white); 
	window.front;
	window.drawFunc = {
		var to_window = {|pos|
			pos.linlin(data_bounds.leftTop, data_bounds.rightBottom, Point(0,0), window.bounds.extent)
		};
		Pen.strokeColor = Color.black;	
		agents.do{|a|
			Pen.addOval( Rect.aboutPoint( to_window.(a.pos), 10, 10 ));
		};
		Pen.fillStroke;
	};
	
	loop{
		window.refresh;
		0.016.wait
	}
	
});


agent_routine.play;
window_routine.play(AppClock);
)

Break down…

Define the bounds of the data, this is what portion of the space that will be drawn, also, I am wrapping the particles inside it.

Define a function to make an agent. Here an agent is just an event with the key pos and vel, position and velocity.

Define an update agent function. This is immutable, meaning the old agent isn’t modified, but used to create a new one.

Define the agents, I’m making 200 and randomly spacing them in the data bounds.

Make a window

Create a routine to update the agents by applied to aforementioned update_agents function to each agent.

Create a routine to update and initialise the window first, define color and stuff, then define the draw function. Then loop and wait, calling refresh each cycle which will cause the draw function to be re-evaluated.
The draw function breaks down as follows.
Define a function to map from the data bounds to the screen bounds. Set the pen colour, make a circle at each agent’s position, fill them.

Then run both the routines.

I’d recommend you have a routine for the processing and the window as you will probably want to update them at different speeds.

Using a UserView is probably better, rather than the window, but if you just want to see them, this will suffice.

1 Like

Thanks, @jordan! I will try! The reason Why I use the class is I will add more functions and separate the visual and sound playing situation in code!

In the code, I can’t use the linlin.

var to_window = {|pos|
			pos.linlin(data_bounds.leftTop, data_bounds.rightBottom, Point(0,0), window.bounds.extent)

after checking in your code I re write the code

class

Agent {
    var <x, <y, vx, vy;

	*new { | x=0, y=0, vx=0.1, vy=0.1 |
        ^super.newCopyArgs(x, y, vx, vy);
    }

    update { | dt |
        x = x + vx * dt;
        y = y + vy * dt;
		^this;
    }
}

this is code

(
// create multiple agents
var agents = Array.fill(10, { Agent.new(0.2.rand, 0.2.rand, 0.1.rand, 0.1.rand) });

// create a window to display agents' positions
var window = Window.new("Agent Positions", Rect(0, 0, 400, 400));
window.view.decorator = FlowLayout(window.view.bounds);
window.onClose = { "Window closed".postln };


Task({
    loop {
        agents.do({ |agent|
			agent.update(-1.0.rrand(1.0));
			// [agent.x, agent.y].postln;
        });
        0.1.wait;
    }
}).play;



// draw agents on the window
window.drawFunc = {
	// var g = window.view.graphics;
	window.background = Color.white;

	// draw agents as circles
	Pen.strokeColor = Color.black;
	agents.do { | agent |
		agent.update(-1.0.rrand(1.0));
		// g.color = Color.black;
		Pen.addOval(Rect(agent.x*400, agent.y*400, 5, 5));
	};
	Pen.fillStroke;
};



// open the window
window.front;
)

In this code, I would like to check the changing position in the task’s wait time,
but I don’t know how to merge the Task function and window.drawFunction

(
// create multiple agents
var agents = Array.fill(10, { Agent.new(-0.5.rrand(0.5), -0.5.rrand(0.5), 0.1.rand, 0.1.rand) });

// create a window to display agents' positions
var window = Window.new("Agent display2", Rect(0, 0, 1000, 1000));

/*var agent_routine = Routine({
	loop {
		agents = agents.update(2);
		0.016.wait;
	}
});*/


var window_routine = Routine({
	window.view.background_(Color.white);
	window.front;
	window.drawFunc = {
		// var to_window = agents.update(2);
		Pen.strokeColor = Color.black;
		agents.do{|agent|
			Pen.addOval(Rect(agent.x*1000, agent.y*1000, 5, 5));
			agent.update(0.9);
		};
		Pen.fillStroke;
	};

	inf.do{
		window.refresh;
		agents.update(10);
		0.016.wait
	}

});

window_routine.play(AppClock);

)

I try to understand your code but It was a bit hard to me so I wrote it as a different way. I need to do test something more but for now It works! thanks!

Like this:

Task({
    loop {
        agents.do({ |agent|
			agent.update(-1.0.rrand(1.0));
			// [agent.x, agent.y].postln;
        });
        defer { window.refresh };
        0.1.wait;
    }
}).play;

If it’s my code, I wouldn’t run two routines with different timing. I’d sync the graphics updates to the data updates like shown here.

hjh

Point.linlin must be some quark I’ve installed, it’s hard to check sometimes, sorry about that.

So you have interlaced the update logic and the window drawing logic, if you wanted the separations of concerns you mentioned before you’ve immediately gotten rid of it here.

I separated the process into two routines to make this clear, it would also avoid redrawing the window really fast. This will be more of an issue if you have the update logic controlled by some hardware, network or server input. However, you don’t have to do that, the issue is that you are interlacing the updating of the agents with their drawing. These two things are not connected and should be separated somehow, usually by different functions.

The idea is you do all the updating in one function, then pass an immutable version of the data to the drawing function.

Here you are, inside of the draw loop, drawing an oval at the agents positions at T1 then updating (T2), then later you update again (T3) in a different loop.

My fault, it must have been a part of a quark. Below should work, haven’t tested it though as I’m away from the computer. This is a little longer than it could be as I’m wrapping the agents in a space, that way, you can always see them on the screen - defined with data_bounds.

(
var point_linlin = {|p, fromlow, fromhigh, tolow, tohigh|
   Point(
      p.x.linlin(fromlow.x, fromhigh.x, tolow.x, tohigh.x),
      p.y.linlin(fromlow.y, fromhigh.y, tolow.y, tohigh.y)
   )
};

var data_bounds = Rect.newSides(left: -10, top: -10, right: 10, bottom: 10);
var mk_agent = { |pos, vel| (\pos: pos ? Point(), \vel: vel ? Point()) };

var update_agent = {|a, dt=0.01, coords|
	mk_agent.(
		pos: ( a.pos + (a.vel * dt) ).wrap(coords.leftTop, coords.rightBottom), 
		vel: a.vel
	)
};

var agents = 200.collect({ 
	mk_agent.(
		pos: Point(
			data_bounds.left + data_bounds.extent.x.rand2, 
			data_bounds.top + data_bounds.extent.x.rand2
		),
		vel: Point( 5.0.rand2, 5.0.rand2)	
	) 
});

var window = Window.new("agent display");

var agent_routine = Routine({
	var delta_t = 0.01;
	loop { 
		agents = agents.collect( update_agent.(_, dt: delta_t, coords: data_bounds) );
		delta_t.wait;
	}
});


var window_routine = Routine({
	window.view.background_(Color.white); 
	window.front;
	window.drawFunc = {
		var to_window = { |pos|  
            point_linlin.(pos, data_bounds.leftTop, data_bounds.rightBottom, Point(0,0), window.bounds.extent)
		};
		Pen.strokeColor = Color.black;	
		agents.do{|a|
			Pen.addOval( Rect.aboutPoint( to_window.(a.pos), 10, 10 ));
		};
		Pen.fillStroke;
	};
	
	loop{
		window.refresh;
		0.016.wait
	}
	
});


agent_routine.play;
window_routine.play(AppClock);
)