So I have all these classes put together for all the connectivity between Predators and Prey and the world. The only thing I'm really stumped on is the run() method for the Predator class (how they hunt).
The theory is simple. The predators have to gather around the Prey on it's North, South, East and West side, and the DataChannel class will notice that and capture the prey and take it off the map. But my job is to get this to happen, by having the Predators communicate with one another, and then chase and hunt down the Prey (who I programmed to move randomly).
Here's all the classes. Remember, the run() method for the Predator class is where I'm stumped. Everything else is how I want it to be. Any help?
/**
Predator class, with no "hunting" functionality.
*/
import java.io.*;
import javax.imageio.ImageIO;
import java.util.ArrayList;
import javaclient2.*;
import javaclient2.structures.*;
public class Predator extends Thread
{
private Position2DInterface position_interface = null;
private BlobfinderInterface blob_finder = null;
private PlayerClient playerClient = null;
private DataChannel dc = null;
private String name = "";
public Predator(String name, DataChannel dc, int id, float x, float y){
this.name = name;
this.playerClient = new PlayerClient("localhost", 6665);
blob_finder = playerClient.requestInterfaceBlobfinder(id,
PlayerConstants.PLAYER_OPEN_MODE);
position_interface = playerClient.requestInterfacePosition2D(id,
PlayerConstants.PLAYER_OPEN_MODE);
playerClient.runThreaded (-1, -1);
//wait until the intefaces are ready before doing anything
while(!blob_finder.isDataReady() ||
!position_interface.isDataReady()) {
try{
sleep(100);
}catch(Exception e){
System.err.println("Error sleeping!");
e.printStackTrace();
System.exit(-1);
}
}
PlayerPose pp = new PlayerPose();
pp.setPx(x);
pp.setPy(y);
position_interface.setOdometry(pp);
this.dc = dc;
dc.registerPredator(name, position_interface);
}
/**
* #param recipient The predator to deliver the message to.
* #param msg The message.
*
* Deliver a message to another predator.
*
*/
public void sendMessage(String recipient, Object msg){
dc.sendMessage(recipient, msg);
}
/**
* #param msg The message.
*
* Deliver a message to all other predators.
*
*/
public void broadcastMessage(Object msg){
for(String predator : dc.getPredators()){
sendMessage(predator, msg);
}
}
/**
*
* Get the next message from other predators.
*
* #return The next message, or null if there are no unread messages.
*
*/
public Object getMessage(){
return dc.getMessage(this.name);
}
public void run(){
// hunt the prey!
System.out.println("There are " + dc.numLivingPreys() +
" left to capture!");
}
}
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Vector;
import java.util.Set;
import javaclient2.*;
import javaclient2.structures.*;
/**
Object that records all of the predator locations, and kills prey when
they have been captured.
*/
public class DataChannel extends Thread{
static final float FUDGE_FACTOR = 1;
static final float CAPTURE_RANGE = 5;
private ConcurrentHashMap<String, Position2DInterface> pred_pids =
new ConcurrentHashMap<String, Position2DInterface>();
private ConcurrentHashMap<String, Position2DInterface> prey_pids =
new ConcurrentHashMap<String, Position2DInterface>();
private ConcurrentHashMap<String, Prey> preys =
new ConcurrentHashMap<String, Prey>();
private ConcurrentHashMap<String, ConcurrentLinkedQueue<Object>> msgs =
new ConcurrentHashMap<String, ConcurrentLinkedQueue<Object>>();
public void registerPredator(String name, Position2DInterface pid){
pred_pids.put(name, pid);
msgs.put(name, new ConcurrentLinkedQueue<Object>());
}
public void registerPrey(String name, Position2DInterface pid, Prey prey){
prey_pids.put(name, pid);
preys.put(name, prey);
}
public int numLivingPreys(){
return preys.size();
}
public Set<String> getPredators(){
return msgs.keySet();
}
public void sendMessage(String recipient, Object msg){
(msgs.get(recipient)).add(msg);
}
public Object getMessage(String recipient){
return (msgs.get(recipient)).poll();
}
public float getPredX(String predator){
try{
return (pred_pids.get(predator)).getX();
}catch(Exception ex) {}
return -1.0f;
}
public float getPredY(String predator){
try{
return (pred_pids.get(predator)).getY();
}catch(Exception ex) {}
return -1.0f;
}
public float getPreyX(String prey){
try{
return (prey_pids.get(prey)).getX();
}catch(Exception ex) {}
return -1.0f;
}
public float getPreyY(String prey){
try{
return (prey_pids.get(prey)).getY();
}catch(Exception ex) {}
return -1.0f;
}
public void run(){
while(true){
try{
sleep(100);
}catch(Exception e){
System.err.println("Error sleeping!");
e.printStackTrace();
System.exit(-1);
}
//get the location of each predator
Vector<Float> xpos = new Vector<Float>();
Vector<Float> ypos = new Vector<Float>();
Vector<String> pred_names= new Vector<String>();
for(String predator : pred_pids.keySet()){
if(pred_pids.get(predator) == null){
System.err.println("pred_pids does not have " + predator);
System.exit(-1);
}
xpos.add(getPredX(predator));
ypos.add(getPredY(predator));
pred_names.add(predator);
}
//for each prey, see if all of the four positions are guarded
for(String prey : prey_pids.keySet()){
boolean north = false;
boolean south = false;
boolean east = false;
boolean west = false;
if(prey_pids.get(prey) == null){
System.err.println("prey_pids does not have " + prey);
System.exit(-1);
}
float prey_x = getPreyX(prey);
float prey_y = getPreyY(prey);
for(int i=0; i < xpos.size(); i++){
//NORTH
if(Math.abs(xpos.get(i) - prey_x)<FUDGE_FACTOR &&
(ypos.get(i) - prey_y) > 0 &&
(ypos.get(i) - prey_y) < CAPTURE_RANGE){
north = true;
}
//SOUTH
if(Math.abs(xpos.get(i) - prey_x)<FUDGE_FACTOR &&
(prey_y - ypos.get(i)) > 0 &&
(prey_y - ypos.get(i)) < CAPTURE_RANGE){
south = true;
}
//EAST
if(Math.abs(ypos.get(i) - prey_y)<FUDGE_FACTOR &&
(xpos.get(i) - prey_x) > 0 &&
(xpos.get(i) - prey_x) < CAPTURE_RANGE){
east = true;
}
//WEST
if(Math.abs(ypos.get(i) - prey_y)<FUDGE_FACTOR &&
(prey_x - xpos.get(i)) > 0 &&
(prey_x - xpos.get(i)) < CAPTURE_RANGE){
west = true;
}
}
//prey is boxed in
if(north && south && east && west){
(preys.get(prey)).die();
preys.remove(prey);
prey_pids.remove(prey);
}
}
if(preys.size() == 0){
System.err.println("Congratulations: All prey are captured.");
System.exit(0);
}
}
}
}
import javaclient2.structures.*;
import javaclient2.*;
import java.util.Random;
/**
Prey class.
*/
public class Prey extends Thread{
private final int ROTATE_SECONDS = 500;
private final int MOVE_SECONDS = 3000;
private final int WAIT_SECONDS = 8000;
private final int WAIT_JITTER = 4000;
private Position2DInterface position_interface = null;
private PlayerClient playerClient = null;
private DataChannel dc = null;
private String my_name = null;
private boolean keep_going = true;
Random rand = new Random();
public Prey(String name, DataChannel dc, int id, float x, float y){
this.playerClient = new PlayerClient("localhost", 6665);
position_interface = playerClient.requestInterfacePosition2D(id,
PlayerConstants.PLAYER_OPEN_MODE);
playerClient.runThreaded (-1, -1);
this.dc = dc;
this.my_name = name;
while(!position_interface.isDataReady()) {
try{
sleep(100);
}catch(Exception e){
System.err.println("Error sleeping!");
e.printStackTrace();
System.exit(-1);
}
}
PlayerPose pp = new PlayerPose();
pp.setPx(x);
pp.setPy(y);
position_interface.setOdometry(pp);
dc.registerPrey(name, position_interface, this);
}
public float getX(){
try{
return position_interface.getX();
}catch(Exception ex) {}
return -1.0f;
}
public float getY(){
try{
return position_interface.getY();
}catch(Exception ex) {}
return -1.0f;
}
public void run(){
float old_x = getX();
float old_y = getY();
while(keep_going){
float current_x = getX();
float current_y = getY();
float x, y;
if(current_x <=0){
if(rand.nextFloat() < 0.75)
x = rand.nextFloat()*6;
else
x = rand.nextFloat()*-6;
}else{
if(rand.nextFloat() < 0.75)
x = rand.nextFloat()*-6;
else
x = rand.nextFloat()*6;
}
if(current_y <=0){
if(rand.nextFloat() < 0.75)
y = rand.nextFloat()*12;
else
y = rand.nextFloat()*-12;
}else{
if(rand.nextFloat() < 0.75)
y = rand.nextFloat()*-12;
else
y = rand.nextFloat()*12;
}
PlayerPose pp = new PlayerPose();
pp.setPx(x);
pp.setPy(y);
position_interface.setVelocity(pp, 0);
sleep(MOVE_SECONDS);
position_interface.setSpeed(0.0f, 0.0f);
sleep(WAIT_SECONDS + rand.nextInt() % WAIT_JITTER);
}
position_interface.setSpeed(9999.0f, 0.0f);
}
public void sleep(int ms){
try{
Thread.sleep(ms);
}catch(Exception e){
System.err.println("Error sleeping.");
e.printStackTrace();
System.exit(-1);
}
}
public float angle_diff(float current, float desired)
{
float diff = desired - current;
while(diff > 180.0f) diff -= 360.0f;
while(diff < -180.0f) diff += 360.0f;
return diff;
}
public float fix_angle(float f){
while(f < 0.0f) f += 360.0f;
while(f > 360.0f) f -= 360.0f;
return f;
}
public void die(){
System.err.println("Prey \"" + this.my_name + "\" has been killed!");
this.keep_going = false;
}
}
public class Driver{
public static void main(String args[]){
DataChannel dc = new DataChannel();
//instantiate the predators
Predator pred1 = new Predator("pred1", dc, 0, 0, 13);
Predator pred2 = new Predator("pred2", dc, 1, 0, 0);
Predator pred3 = new Predator("pred3", dc, 2, 0, -13);
Predator pred4 = new Predator("pred4", dc, 3, -13, -8);
Predator pred5 = new Predator("pred5", dc, 4, -13, 8);
//instantiate the prey
Prey prey1 = new Prey("prey1", dc, 5, 18, 18);
Prey prey2 = new Prey("prey2", dc, 6, 18, -18);
Prey prey3 = new Prey("prey3", dc, 7, 18, -9);
Prey prey4 = new Prey("prey4", dc, 8, 18, 9);
//start all the threads
dc.start();
pred1.start();
pred2.start();
pred3.start();
pred4.start();
pred5.start();
prey1.start();
prey2.start();
prey3.start();
prey4.start();
}
}
The predators don't actually need to communicate. They just need to locate prey, and move as close as possible to it.
So, if p1 and p2 represents predators and o1 represents prey:
A B C D
0 . . . .
1 . p2 . .
2 . . p1 .
3 . . o1 .
p1 is as close as it can get to o1, so it stays put.
p2, however, can get closer by moving to B3.
Now, in your sample code, you have 4 predators and 5 prey. This could lead to a case where there are not enough predators focused on one prey to eliminate it. For that to work, you need a heuristic like: "prefer the prey with the most predators".
You may also need to consider the case where both sides are equal. You could wind up with one predator per prey. That can be handled by having predators give up if a period elapses without their prey being eliminated. You will want to include some randomness so that not all the predators give up at the same time. Something like baseGiveUpTime + (int)(2 * numPred * Math.random())
Related
I'm new to the Android game making scene and for my first game, I made a successful Flappy Bird clone thanks to LibGdx, coffee, and Youtube. To make it NOT be a total clone however, I'm trying to add different types of pipes and this is where I need help with.
I'm trying to make it so that the 4 different types of pipes (or trees, as they are in my game) would appear randomly to make it unpredictable but so far I've only ever managed to make 1 type appear. Here are the codes I used for 2 of the types and for my Play State.
HTree class implementation:
public class HTree {
public static final int TREE_WIDTH = 52;
private static final int FLUCTUACTION = 130;
private static final int TREE_GAP = 100;
private static final int LOWEST_OPENING = 120;
private Texture toptree,bottomtree;
private Vector2 postoptree,posbottree;
private Rectangle boundsTop,boundsBot;
private Random rand;
public HTree(float x){
toptree = new Texture("uppertree.png");
bottomtree = new Texture("bottomtree.png");
rand = new Random();
postoptree = new Vector2(x, rand.nextInt(FLUCTUACTION) + TREE_GAP + LOWEST_OPENING);
posbottree = new Vector2(x, postoptree.y - TREE_GAP - bottomtree.getHeight());
boundsTop = new Rectangle(getPostoptree().x,getPostoptree().y, toptree.getWidth(), toptree.getHeight());
boundsBot = new Rectangle(getPosbottree().x,getPosbottree().y, bottomtree.getWidth(), bottomtree.getHeight());
}
public Texture getToptree() {
return toptree;
}
public Texture getBottomtree() {
return bottomtree;
}
public Vector2 getPostoptree() {
return postoptree;
}
public Vector2 getPosbottree() {
return posbottree;
}
public void reposition(float x){
postoptree.set(x, rand.nextInt(FLUCTUACTION) + TREE_GAP + LOWEST_OPENING);
posbottree.set(x, postoptree.y - TREE_GAP - bottomtree.getHeight());
boundsTop.setPosition(postoptree.x,postoptree.y);
boundsBot.setPosition(posbottree.x,posbottree.y);
}
public boolean collides(Rectangle player){
return player.overlaps(boundsTop) || player.overlaps(boundsBot);
}
public void dispose(){
toptree.dispose();
bottomtree.dispose();
}
}
HTree2 class implementation:
public class HTree2 {
public static final int TREE_WIDTH = 52;
private static final int FLUCTUACTION = 130;
private static final int TREE_GAP = 100;
private static final int LOWEST_OPENING = 120;
private Texture toptree2,bottomtree2;
private Vector2 postoptree2,posbottree2;
private Rectangle boundsTop2,boundsBot2;
private Random rand;
public HTree2(float x){
toptree2 = new Texture("uppertree2.png");
bottomtree2 = new Texture("bottomtree2.png");
rand = new Random();
postoptree2 = new Vector2(x, rand.nextInt(FLUCTUACTION) + TREE_GAP + LOWEST_OPENING);
posbottree2 = new Vector2(x, postoptree2.y - TREE_GAP - bottomtree2.getHeight());
boundsTop2 = new Rectangle(getPostoptree().x,getPostoptree().y, toptree2.getWidth(), toptree2.getHeight());
boundsBot2 = new Rectangle(getPosbottree().x,getPosbottree().y, bottomtree2.getWidth(), bottomtree2.getHeight());
}
public Texture getToptree() {
return toptree2;
}
public Texture getBottomtree() {
return bottomtree2;
}
public Vector2 getPostoptree() {
return postoptree2;
}
public Vector2 getPosbottree() {
return posbottree2;
}
public void reposition2(float x){
postoptree2.set(x, rand.nextInt(FLUCTUACTION) + TREE_GAP + LOWEST_OPENING);
posbottree2.set(x, postoptree2.y - TREE_GAP - bottomtree2.getHeight());
boundsTop2.setPosition(postoptree2.x,postoptree2.y);
boundsBot2.setPosition(posbottree2.x,posbottree2.y);
}
public boolean collides2(Rectangle player){
return player.overlaps(boundsTop2) || player.overlaps(boundsBot2);
}
public void dispose2(){
toptree2.dispose();
bottomtree2.dispose();
}
}
HPlayState class implementation:
public class HPlayState extends HState {
private static final int TREE_SPACING = 125;
private static final int TREE_COUNT = 4;
private static final int GROUND_Y_OFFSET = -50;
private HBird bird;
private Texture bg;
private Texture ground;
private Vector2 groundPos1,groundPos2;
private Array<HTree> trees;
private Array<HTree2> trees2;
public HPlayState(HGameStateManager gsm) {
super(gsm);
bird = new HBird(50,300);
cam.setToOrtho(false, HBonGame.WIDTH/2,HBonGame.HEIGHT/2);
bg = new Texture("background3.jpg");
ground = new Texture("ground.png");
groundPos1 = new Vector2(cam.position.x-cam.viewportWidth/2,GROUND_Y_OFFSET);
groundPos2 = new Vector2((cam.position.x-cam.viewportWidth/2) + ground.getWidth(),GROUND_Y_OFFSET);
trees = new Array<HTree>();
trees2 = new Array<HTree2>();
for(int i=1; i <= TREE_COUNT; i++){
trees.add(new HTree(i * (TREE_SPACING + HTree.TREE_WIDTH)));
}
for (int i=1; i >= TREE_COUNT; i++){
trees2.add(new HTree2(i * (TREE_SPACING + HTree2.TREE_WIDTH)));
}
}
#Override
protected void handleInput() {
if (Gdx.input.justTouched())
bird.jump();
}
#Override
public void update(float dt) {
handleInput();
updateGround();
bird.update(dt);
cam.position.x = bird.getPosition().x + 80;
for (int i=0; i < trees.size; i++ ){
HTree tree = trees.get(i);
if (cam.position.x - (cam.viewportWidth / 2) > tree.getPostoptree().x + tree.getToptree().getWidth()){
tree.reposition(tree.getPostoptree().x + ((HTree.TREE_WIDTH + TREE_SPACING) * TREE_COUNT));
}
if(tree.collides(bird.getBounds())){
gsm.set(new HGameOverState(gsm));
}
}
for (int i=0; i < trees2.size; i++ ){
HTree2 tree2 = trees2.get(i);
if (cam.position.x - (cam.viewportWidth / 2) > tree2.getPostoptree().x + tree2.getToptree().getWidth()){
tree2.reposition2(tree2.getPostoptree().x + ((HTree2.TREE_WIDTH + TREE_SPACING) * TREE_COUNT));
}
if(tree2.collides2(bird.getBounds())){
gsm.set(new HGameOverState(gsm));
}
}
if (bird.getPosition().y <= ground.getHeight()+GROUND_Y_OFFSET){
gsm.set(new HGameOverState(gsm));
}
cam.update();
}
#Override
public void render(SpriteBatch sb) {
sb.setProjectionMatrix(cam.combined);
sb.begin();
sb.draw(bg,cam.position.x - (cam.viewportWidth/2), 0);
sb.draw(bird.getTexture(),bird.getPosition().x,bird.getPosition().y);
for (HTree tree : trees) {
sb.draw(tree.getToptree(), tree.getPostoptree().x, tree.getPostoptree().y);
sb.draw(tree.getBottomtree(), tree.getPosbottree().x, tree.getPosbottree().y);
}
for (HTree2 tree2 : trees2) {
sb.draw(tree2.getToptree(), tree2.getPostoptree().x, tree2.getPostoptree().y);
sb.draw(tree2.getBottomtree(), tree2.getPosbottree().x, tree2.getPosbottree().y);
}
sb.draw(ground, groundPos1.x,groundPos1.y);
sb.draw(ground, groundPos2.x,groundPos2.y);
sb.end();
}
#Override
public void dispose() {
bg.dispose();
bird.dispose();
ground.dispose();
for (HTree tree : trees){
tree.dispose();
}
for (HTree2 tree2 : trees2){
tree2.dispose2();
}
}
private void updateGround() {
if (cam.position.x - (cam.viewportWidth / 2) > groundPos1.x + ground.getWidth())
groundPos1.add(ground.getWidth() * 2, 0);
if (cam.position.x - (cam.viewportWidth / 2) > groundPos2.x + ground.getWidth())
groundPos2.add(ground.getWidth() * 2, 0);
}
#Override
public void resize(int width, int height) {
bird = new HBird(50,300);
}
}
I would recommend using an interface or a superclass of type HTree which all the other types can then extend from. E.g.
public abstract class HTree {
/*
All your class variables as declared
in your question
*/
public HTree(float x, String topTreeFile, String bottomTreeFile) {
/* Do all the setup stuff here */
// Set the textures
toptree = new Texture(topTreeFile);
bottomtree = new Texture(bottomTreeFile);
}
/*
All your normal getters and setters
and other functions here
*/
}
Then you would create 4 different classes which all extend from HTree as follows:
/* Different types of tree each extend the main HTree class */
public class HTree1 extends HTree {
public HTree1(float x) {
super(x, "uppertree.png", "lowertree.png");
/* Any other custom code for this tree type
can go here */
}
}
public class HTree2 extends HTree {
public HTree1(float x) {
super(x, "uppertree2.png", "lowertree2.png");
/* Any other custom code for this tree type
can go here */
}
}
/* And so on for HTree3 and HTree4 ... */
Finally, you can use any of these four different types as a HTree because they all extend from that class so they can be grouped into a single Array<HTree> and can be rendered/updated together in a single loop.
public class HPlayState extends HState {
/* Other class variables here ... */
/* Only one array for all different tree types */
private Array<HTree> trees;
public HPlayState(GameStateManager gsm) {
/* Same setup code ... */
trees = new Array<HTree>();
Random random = new Random();
for (int i = 1; i <= TREECOUNT; i++) {
/* Randomly pick which tree type to choose */
int treeType = rand.next(4); // 4 or however many different types of tree you have
float x = i * (TREE_SPACING + HTree.TREE_WIDTH);
if (treeType == 0) trees.add(new HTree1(x));
else if (treeType == 1) trees.add(new HTree2(x));
else if (treeType == 2) trees.add(new HTree3(x));
else trees.add(new HTree4(x));
}
}
/*
Rendering and updating code here
*/
}
Note: I haven't tested any of the code so there a might be a few typos or changes needed but it should work fine.
I have JFrame with a start button, which triggers the calculation of a Julia Set.
The code that is executed when the start button is clicked is as follows:
public void actionPerformed(ActionEvent aActionEvent)
{
String strCmd = aActionEvent.getActionCommand();
if (strCmd.equals("Start"))
{
m_cCanvas.init();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_bRunning = true;
this.handleCalculation();
}
else if (aActionEvent.getSource() == m_cTReal)
Which used to work fine, except that the application could not be closed anymore. So I tried to use m_bRunning in a separate method so that actionPerformed() isn't blocked all the time to see if that would help, and then set m_bRunning = false in the method stop() which is called when the window is closed:
public void run()
{
if(m_bRunning)
{
this.handleCalculation();
}
}
The method run() is called from the main class in a while(true) loop.
Yet unfortunately, neither did that solve the problem, nor do I now have any output to the canvas or any debug traces with System.out.println(). Could anyone point me in the right direction on this?
EDIT:
Here are the whole files:
// cMain.java
package juliaSet;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.Dimension;
public class cMain {
public static void main(String[] args)
{
int windowWidth = 1000;//(int)screenSize.getWidth() - 200;
int windowHeight = 800;//(int)screenSize.getHeight() - 50;
int plotWidth = 400;//(int)screenSize.getWidth() - 600;
int plotHeight = 400;//(int)screenSize.getHeight() - 150;
JuliaSet cJuliaSet = new JuliaSet("Julia Set", windowWidth, windowHeight, plotWidth, plotHeight);
cJuliaSet.setVisible(true);
while(true)
{
cJuliaSet.run();
}
}
}
// JuliaSet.java
package juliaSet;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import java.util.Random;
import java.io.*;
import java.lang.ref.*;
public class JuliaSet extends JFrame implements ActionListener
{
private JButton m_cBStart;
private JTextField m_cTReal;
private JTextField m_cTImag;
private JTextField m_cTDivergThresh;
private JLabel m_cLReal;
private JLabel m_cLImag;
private JLabel m_cLDivergThresh;
private int m_iDivergThresh = 10;
private String m_cMsgDivThresh = "Divergence threshold = " + m_iDivergThresh;
private JuliaCanvas m_cCanvas;
private int m_iPlotWidth; // number of cells
private int m_iPlotHeight; // number of cells
private Boolean m_bRunning = false;
private double m_dReal = 0.3;
private double m_dImag = -0.5;
private String m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
private String m_cMsgIter = "x = 0, y = 0";
private Complex m_cCoordPlane[][];
private double m_dAbsSqValues[][];
private int m_iIterations[][];
private Complex m_cSummand;
private BufferedImage m_cBackGroundImage = null;
private FileWriter m_cFileWriter;
private BufferedWriter m_cBufferedWriter;
private String m_sFileName = "log.txt";
private Boolean m_bWriteLog = false;
private static final double PLOTMAX = 2.0; // we'll have symmetric axes
// ((0,0) at the centre of the
// plot
private static final int MAXITER = 0xff;
JuliaSet(String aTitle, int aFrameWidth, int aFrameHeight, int aPlotWidth, int aPlotHeight)
{
super(aTitle);
this.setSize(aFrameWidth, aFrameHeight);
m_iPlotWidth = aPlotWidth;
m_iPlotHeight = aPlotHeight;
m_cSummand = new Complex(m_dReal, m_dImag);
m_cBackGroundImage = new BufferedImage(aFrameWidth, aFrameHeight, BufferedImage.TYPE_INT_RGB);
this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
stop();
super.windowClosing(e);
System.exit(0);
}
});
GridBagLayout cLayout = new GridBagLayout();
GridBagConstraints cConstraints = new GridBagConstraints();
this.setLayout(cLayout);
m_cCanvas = new JuliaCanvas(m_iPlotWidth, m_iPlotHeight);
m_cCanvas.setSize(m_iPlotWidth, m_iPlotHeight);
m_cBStart = new JButton("Start");
m_cBStart.addActionListener(this);
m_cTReal = new JTextField(5);
m_cTReal.addActionListener(this);
m_cTImag = new JTextField(5);
m_cTImag.addActionListener(this);
m_cTDivergThresh = new JTextField(5);
m_cTDivergThresh.addActionListener(this);
m_cLReal = new JLabel("Re(c):");
m_cLImag = new JLabel("Im(c):");
m_cLDivergThresh = new JLabel("Divergence Threshold:");
cConstraints.insets.top = 3;
cConstraints.insets.bottom = 3;
cConstraints.insets.right = 3;
cConstraints.insets.left = 3;
// cCanvas
cConstraints.gridx = 0;
cConstraints.gridy = 0;
cLayout.setConstraints(m_cCanvas, cConstraints);
this.add(m_cCanvas);
// m_cLReal
cConstraints.gridx = 0;
cConstraints.gridy = 1;
cLayout.setConstraints(m_cLReal, cConstraints);
this.add(m_cLReal);
// m_cTReal
cConstraints.gridx = 1;
cConstraints.gridy = 1;
cLayout.setConstraints(m_cTReal, cConstraints);
this.add(m_cTReal);
// m_cLImag
cConstraints.gridx = 0;
cConstraints.gridy = 2;
cLayout.setConstraints(m_cLImag, cConstraints);
this.add(m_cLImag);
// m_cTImag
cConstraints.gridx = 1;
cConstraints.gridy = 2;
cLayout.setConstraints(m_cTImag, cConstraints);
this.add(m_cTImag);
// m_cLDivergThresh
cConstraints.gridx = 0;
cConstraints.gridy = 3;
cLayout.setConstraints(m_cLDivergThresh, cConstraints);
this.add(m_cLDivergThresh);
// m_cTDivergThresh
cConstraints.gridx = 1;
cConstraints.gridy = 3;
cLayout.setConstraints(m_cTDivergThresh, cConstraints);
this.add(m_cTDivergThresh);
// m_cBStart
cConstraints.gridx = 0;
cConstraints.gridy = 4;
cLayout.setConstraints(m_cBStart, cConstraints);
this.add(m_cBStart);
if (m_bWriteLog)
{
try
{
m_cFileWriter = new FileWriter(m_sFileName, false);
m_cBufferedWriter = new BufferedWriter(m_cFileWriter);
} catch (IOException ex) {
System.out.println("Error opening file '" + m_sFileName + "'");
}
}
this.repaint();
this.transformCoordinates();
}
public synchronized void stop()
{
if (m_bRunning)
{
m_bRunning = false;
boolean bRetry = true;
}
if (m_bWriteLog)
{
try {
m_cBufferedWriter.close();
m_cFileWriter.close();
} catch (IOException ex) {
System.out.println("Error closing file '" + m_sFileName + "'");
}
}
}
public void collectGarbage()
{
Object cObj = new Object();
WeakReference ref = new WeakReference<Object>(cObj);
cObj = null;
while(ref.get() != null) {
System.gc();
}
}
public void setSummand(Complex aSummand)
{
m_cSummand.setIm(aSummand.getIm());
m_dImag = aSummand.getIm();
m_cSummand.setRe(aSummand.getRe());
m_dReal = aSummand.getRe();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
}
public void paint(Graphics aGraphics)
{
Graphics cScreenGraphics = aGraphics;
// render on background image
aGraphics = m_cBackGroundImage.getGraphics();
this.paintComponents(aGraphics);
// drawString() calls are debug code only....
aGraphics.setColor(Color.BLACK);
aGraphics.drawString(m_cSMsg, 10, 450);
aGraphics.drawString(m_cMsgIter, 10, 465);
aGraphics.drawString(m_cMsgDivThresh, 10, 480);
// rendering is done, draw background image to on screen graphics
cScreenGraphics.drawImage(m_cBackGroundImage, 0, 0, null);
}
public void actionPerformed(ActionEvent aActionEvent)
{
String strCmd = aActionEvent.getActionCommand();
if (strCmd.equals("Start"))
{
m_cCanvas.init();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_bRunning = true;
}
else if (aActionEvent.getSource() == m_cTReal)
{
m_dReal = Double.parseDouble(m_cTReal.getText());
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_cSummand.setRe(m_dReal);
}
else if (aActionEvent.getSource() == m_cTImag)
{
m_dImag = Double.parseDouble(m_cTImag.getText());
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_cSummand.setIm(m_dImag);
}
else if (aActionEvent.getSource() == m_cTDivergThresh)
{
m_iDivergThresh = Integer.parseInt(m_cTDivergThresh.getText());
m_cMsgDivThresh = "Divergence threshold = " + m_iDivergThresh;
}
this.update(this.getGraphics());
}
public void transformCoordinates()
{
double dCanvasHeight = (double) m_cCanvas.getHeight();
double dCanvasWidth = (double) m_cCanvas.getWidth();
// init matrix with same amount of elements as pixels in canvas
m_cCoordPlane = new Complex[(int) dCanvasHeight][(int) dCanvasWidth];
double iPlotRange = 2 * PLOTMAX;
for (int i = 0; i < dCanvasHeight; i++)
{
for (int j = 0; j < dCanvasWidth; j++)
{
m_cCoordPlane[i][j] = new Complex((i - (dCanvasWidth / 2)) * iPlotRange / dCanvasWidth,
(j - (dCanvasHeight / 2)) * iPlotRange / dCanvasHeight);
}
}
}
public void calcAbsSqValues()
{
int iCanvasHeight = m_cCanvas.getHeight();
int iCanvasWidth = m_cCanvas.getWidth();
// init matrix with same amount of elements as pixels in canvas
m_dAbsSqValues = new double[iCanvasHeight][iCanvasWidth];
m_iIterations = new int[iCanvasHeight][iCanvasWidth];
Complex cSum = new Complex();
if (m_bWriteLog) {
try
{
m_cBufferedWriter.write("m_iIterations[][] =");
m_cBufferedWriter.newLine();
}
catch (IOException ex)
{
System.out.println("Error opening file '" + m_sFileName + "'");
}
}
for (int i = 0; i < iCanvasHeight; i++)
{
for (int j = 0; j < iCanvasWidth; j++)
{
cSum.setRe(m_cCoordPlane[i][j].getRe());
cSum.setIm(m_cCoordPlane[i][j].getIm());
m_iIterations[i][j] = 0;
do
{
m_iIterations[i][j]++;
cSum.square();
cSum.add(m_cSummand);
m_dAbsSqValues[i][j] = cSum.getAbsSq();
} while ((m_iIterations[i][j] < MAXITER) && (m_dAbsSqValues[i][j] < m_iDivergThresh));
this.calcColour(i, j, m_iIterations[i][j]);
m_cMsgIter = "x = " + i + " , y = " + j;
if(m_bWriteLog)
{
System.out.println(m_cMsgIter);
System.out.flush();
}
if (m_bWriteLog) {
try
{
m_cBufferedWriter.write(Integer.toString(m_iIterations[i][j]));
m_cBufferedWriter.write(" ");
}
catch (IOException ex) {
System.out.println("Error writing to file '" + m_sFileName + "'");
}
}
}
if (m_bWriteLog) {
try
{
m_cBufferedWriter.newLine();
}
catch (IOException ex) {
System.out.println("Error writing to file '" + m_sFileName + "'");
}
}
}
m_dAbsSqValues = null;
m_iIterations = null;
cSum = null;
}
private void calcColour(int i, int j, int aIterations)
{
Color cColour = Color.getHSBColor((int) Math.pow(aIterations, 4), 0xff,
0xff * ((aIterations < MAXITER) ? 1 : 0));
m_cCanvas.setPixelColour(i, j, cColour);
cColour = null;
}
private void handleCalculation()
{
Complex cSummand = new Complex();
for(int i = -800; i <= 800; i++)
{
for(int j = -800; j <= 800; j++)
{
cSummand.setRe(((double)i)/1000.0);
cSummand.setIm(((double)j)/1000.0);
this.setSummand(cSummand);
this.calcAbsSqValues();
this.getCanvas().paint(m_cCanvas.getGraphics());
this.paint(this.getGraphics());
}
}
cSummand = null;
this.collectGarbage();
System.gc();
System.runFinalization();
}
public boolean isRunning()
{
return m_bRunning;
}
public void setRunning(boolean aRunning)
{
m_bRunning = aRunning;
}
public Canvas getCanvas()
{
return m_cCanvas;
}
public void run()
{
if(m_bRunning)
{
this.handleCalculation();
}
}
}
class JuliaCanvas extends Canvas
{
private int m_iWidth;
private int m_iHeight;
private Random m_cRnd;
private BufferedImage m_cBackGroundImage = null;
private int m_iRed[][];
private int m_iGreen[][];
private int m_iBlue[][];
JuliaCanvas(int aWidth, int aHeight)
{
m_iWidth = aWidth;
m_iHeight = aHeight;
m_cRnd = new Random();
m_cRnd.setSeed(m_cRnd.nextLong());
m_cBackGroundImage = new BufferedImage(m_iWidth, m_iHeight, BufferedImage.TYPE_INT_RGB);
m_iRed = new int[m_iHeight][m_iWidth];
m_iGreen = new int[m_iHeight][m_iWidth];
m_iBlue = new int[m_iHeight][m_iWidth];
}
public void init() {
}
public void setPixelColour(int i, int j, Color aColour)
{
m_iRed[i][j] = aColour.getRed();
m_iGreen[i][j] = aColour.getGreen();
m_iBlue[i][j] = aColour.getBlue();
}
private int getRandomInt(double aProbability)
{
return (m_cRnd.nextDouble() < aProbability) ? 1 : 0;
}
#Override
public void paint(Graphics aGraphics)
{
// store on screen graphics
Graphics cScreenGraphics = aGraphics;
// render on background image
aGraphics = m_cBackGroundImage.getGraphics();
for (int i = 0; i < m_iWidth; i++)
{
for (int j = 0; j < m_iHeight; j++)
{
Color cColor = new Color(m_iRed[i][j], m_iGreen[i][j], m_iBlue[i][j]);
aGraphics.setColor(cColor);
aGraphics.drawRect(i, j, 0, 0);
cColor = null;
}
}
// rendering is done, draw background image to on screen graphics
cScreenGraphics.drawImage(m_cBackGroundImage, 1, 1, null);
}
#Override
public void update(Graphics aGraphics)
{
paint(aGraphics);
}
}
class Complex {
private double m_dRe;
private double m_dIm;
public Complex()
{
m_dRe = 0;
m_dIm = 0;
}
public Complex(double aRe, double aIm)
{
m_dRe = aRe;
m_dIm = aIm;
}
public Complex(Complex aComplex)
{
m_dRe = aComplex.m_dRe;
m_dIm = aComplex.m_dIm;
}
public double getRe() {
return m_dRe;
}
public void setRe(double adRe)
{
m_dRe = adRe;
}
public double getIm() {
return m_dIm;
}
public void setIm(double adIm)
{
m_dIm = adIm;
}
public void add(Complex acComplex)
{
m_dRe += acComplex.getRe();
m_dIm += acComplex.getIm();
}
public void square()
{
double m_dReSave = m_dRe;
m_dRe = (m_dRe * m_dRe) - (m_dIm * m_dIm);
m_dIm = 2 * m_dReSave * m_dIm;
}
public double getAbsSq()
{
return ((m_dRe * m_dRe) + (m_dIm * m_dIm));
}
}
I'm quoting a recent comment from #MadProgrammer (including links)
"Swing is single threaded, nothing you can do to change that, all events are posted to the event queue and processed by the Event Dispatching Thread, see Concurrency in Swing for more details and have a look at Worker Threads and SwingWorker for at least one possible solution"
There is only one thread in your code. That thread is busy doing the calculation and can not respond to events located in the GUI. You have to separate the calculation in another thread that periodically updates the quantities that appears in the window. More info about that in the links, courtesy of #MadProgrammer, I insist.
UPDATED: As pointed by #Yusuf, the proper way of launching the JFrame is
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new JuliaSet("Julia Set", windowWidth, windowHeight, plotWidth, plotHeight);
}
});
Set the frame visible on construction and start calculation when the start button is pressed.
First;
Endless loop is not a proper way to do this. This part is loops and taking CPU and never give canvas to refresh screen. if you add below code your code will run as expected. but this is not the proper solution.
cMain.java:
while (true) {
cJuliaSet.run();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Second: you could call run method when start button clicked. But you should create a thread in run method to not freeze screen.
public static void main(String[] args) {
int windowWidth = 1000;// (int)screenSize.getWidth() - 200;
int windowHeight = 800;// (int)screenSize.getHeight() - 50;
int plotWidth = 400;// (int)screenSize.getWidth() - 600;
int plotHeight = 400;// (int)screenSize.getHeight() - 150;
JuliaSet cJuliaSet = new JuliaSet("Julia Set", windowWidth, windowHeight, plotWidth, plotHeight);
cJuliaSet.setVisible(true);
//While loop removed
}
actionPerformed:
if (strCmd.equals("Start")) {
m_cCanvas.init();
m_cSMsg = "c = " + Double.toString(m_dReal) + " + " + "j*" + Double.toString(m_dImag);
m_bRunning = true;
this.run(); // added call run method.
} else if (aActionEvent.getSource() == m_cTReal) {
run method:
public void run()
{
if(m_bRunning)
{
new Thread(){ //Thread to release screen
#Override
public void run() {
JuliaSet.this.handleCalculation();
}
}.start(); //also thread must be started
}
}
As said by #RubioRic, SwingUtilities.invokeLater method is also a part of solution. But you need to check whole of your code and you should learn about Threads.
Within the scope of a paper I am writing at high school I chose to make my own audio-file-to-spectrogram-converter from scratch in order to create landscapes out of these spectrograms.
I already do have my implementation of an FFT and of using that to make a heightmap, a spectrogram. But I often get weird artifacts in the form of vertical stripes when the frequencies get dense, as you can see in the image below.
The example is right at the beginning with a window length of 2048 and on a log^2-scale. The FFT I am using is flawless, I've already compared it to others and they produce the same result.
This is the function which transforms the amplitudes into frequencies and stores them in a 2D-array:
private void transform(int from, int until) {
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++)
chunk[j] = data[0][i*n+j+start];
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
map[i][j] = val;
}
}
}
Now my Question: Where do these vertical stripes come from and how do I get rid of them?
I currently don't employ a window function and every calculation is stringed to one another, which means there is no overlapping. It is the simplest way you can think of making a spectrogram. Could it help introducing a window function or doing each calculation independent of whether the frame was already involved in a previous calculation, that is to say overlapping the frame-windows?
Also, what other ways are there to improve on my basic approach in order to get a better result?
This is the whole class. I feed it the data and all the necessary information from an audio file:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import org.jtransforms.fft.DoubleFFT_1D;
public class Heightmap extends JFrame implements WindowListener{
public static final int LOG_SCALE = 0;
public static final int LOG_SQUARE_SCALE = 1;
public static final int SQUARE_SCALE = 2;
public static final int LINEAR_SCALE = 3;
private BufferedImage heightmap;
private FileDialog chooser;
private JMenuBar menuBar;
private JMenu fileMenu;
private JMenuItem save, close;
private DoubleFFT_1D fft;
private int[][] data;
private double[][] map;
private double[] chunk;
private int width, height, n, start, scale;
private String name;
private boolean inactive;
public Heightmap(int[][] data, int resolution, int start,
int width, int height, int scale, String name) {
this.data = data;
this.n = resolution;
this.start = start;
this.width = width;
this.height = height;
this.scale = scale;
this.name = name;
fft = new DoubleFFT_1D(n);
map = new double[width][height];
heightmap = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
chunk = new double[n];
System.out.println("Starting transformation...");
long time;
time = System.currentTimeMillis();
transform();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for calculation: "+time+" ms");
time = System.currentTimeMillis();
makeHeightmap();
initComponents();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for drawing heightmap: "+time+" ms");
}
private void initComponents() {
this.setSize(width, height);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setTitle(name);
createMenuBar();
chooser = new FileDialog(this, "Choose a directory", FileDialog.SAVE);
chooser.setDirectory("/Users/<user>/Desktop");
this.addMouseListener(new HeightmapMouseListener());
this.addKeyListener(new HeightmapKeyListener());
this.addWindowListener(this);
this.setVisible(true);
}
private void createMenuBar() {
menuBar = new JMenuBar();
fileMenu = new JMenu();
fileMenu.setText("File");
save = new JMenuItem("Save...", KeyEvent.VK_S);
save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.META_DOWN_MASK));
save.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
chooser.setVisible(true);
String fileName = chooser.getFile();
String dir = chooser.getDirectory();
chooser.setDirectory(dir);
if(fileName != null) {
try {
File outputfile = new File(dir + fileName + ".png");
ImageIO.write(heightmap, "png", outputfile);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Saved "+fileName+".png to "+dir);
}
}
});
close = new JMenuItem("Close", KeyEvent.VK_C);
close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK));
close.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
}
});
fileMenu.add(save);
fileMenu.addSeparator();
fileMenu.add(close);
menuBar.add(fileMenu);
this.setJMenuBar(menuBar);
}
public void paint(Graphics g) {
g.drawImage(heightmap, 0, 0, null);
}
private void transform() {
transform(0, width);
}
private void transform(int from, int until) {
double max = Double.MIN_VALUE;
double min = Double.MAX_VALUE;
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++) {
chunk[j] = data[0][i*n+j+start];
}
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
if(val > max)
max = val;
if(val < min)
min = val;
map[i][j] = val;
}
if(min != 0) {
step = max/(max-min);
for(int j=0; j<height; j++)
map[i][j] = (map[i][j]-min)*step;
}
}
}
/*
* Paints heightmap into the BufferedImage
*/
private void makeHeightmap() {
double max = 0;
switch(scale) {
case LOG_SCALE: max = Math.log(findMax(map)+1); break;
case LOG_SQUARE_SCALE: max = Math.pow(Math.log(findMax(map)+1), 2); break;
case SQUARE_SCALE: max = Math.sqrt(findMax(map)); break;
case LINEAR_SCALE: max = findMax(map); break;
default: max = Math.pow(Math.log(findMax(map)+1), 2); break;
}
double stepsize = 255.0/max;
int val, rgb;
for(int x=0; x<width; x++)
for(int y=0; y<height; y++) {
switch(scale) {
case LOG_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); break;
case LOG_SQUARE_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
case SQUARE_SCALE: val = (int) (Math.sqrt(map[x][y])*stepsize); break;
case LINEAR_SCALE: val = (int) (map[x][y]*stepsize); break;
default: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
}
rgb = 255<<24 | val<<16 | val<<8 | val;
heightmap.setRGB(x, height-y-1, rgb);
}
}
private double findMax(double[][] data) {
double max = 0;
for(double[] val1: data)
for(double d: val1)
if(d > max)
max = d;
return max;
}
private class HeightmapKeyListener implements KeyListener {
boolean busy = false;
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_RIGHT && !busy && start < data[0].length-width*n) {
busy = true;
for(int x=0; x<width-1; x++)
map[x] = map[x+1].clone();
start += n;
transform(width-1, width);
makeHeightmap();
repaint();
busy = false;
}
else if(e.getKeyCode() == KeyEvent.VK_LEFT && !busy && start > 0) {
busy = true;
for(int x=width-1; x>0; x--)
map[x] = map[x-1];
start -= n;
transform(0, 1);
makeHeightmap();
repaint();
busy = false;
}
}
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) { }
}
private class HeightmapMouseListener implements MouseListener {
public void mouseClicked(MouseEvent e) {
if(inactive) {
inactive = false;
return;
}
long time = System.currentTimeMillis();
int posX = e.getX();
int diff = posX - width/2; //difference between old and new center in pixels
int oldStart = start;
start = start + diff*n;
if(start < 0) start = 0;
int maxFrame = data[0].length-width*n;
if(start > maxFrame) start = maxFrame;
if(start == oldStart) return;
System.out.println("Changing center...");
int absDiff = Math.abs(diff);
if(start < oldStart) { //shift the start backward, recalculate the start
for(int x=width-1; x>=absDiff; x--)
map[x] = map[x-absDiff].clone();
transform(0, absDiff);
}
else if(start > oldStart) { //shift the back forward, recalculate the back
for(int x=0; x<width-absDiff; x++)
map[x] = map[x+absDiff].clone();
transform(width-absDiff, width);
}
makeHeightmap();
repaint();
System.out.println("Time taken: "+(System.currentTimeMillis()-time)+" ms");
}
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
}
public void windowActivated(WindowEvent arg0) { }
public void windowClosed(WindowEvent arg0) { }
public void windowClosing(WindowEvent arg0) { }
public void windowDeactivated(WindowEvent arg0) {
inactive = true;
}
public void windowDeiconified(WindowEvent arg0) { }
public void windowIconified(WindowEvent arg0) { }
public void windowOpened(WindowEvent arg0) { }
}
EDIT:
Implementing a window function improved the result drastically. I really didn't understand what a window function would do and therefore underestimated its effect.
However, after doing so I tried mapping a cosine wave with a frequency of 10kHz which (again) produced some strange artifacts:
What could be the cause of this one? I implemented a overflow protection by clipping everything under 0 to 0 and over 255 to 255 with no change whatsoever.
This type of artifact can be due to overflow or otherwise exceeding parameter bounds before or in your color mapping function, or perhaps with some function (log?) returning NaN values. You might be able to find this by putting in some asserts for out-of-range or illegal values.
We are making a six-player multi-player game for school and I am responsible for a networking class. The game incorporates Slick 2D. In the game there are entities (code shown below)
package Game;
import org.newdawn.slick.*;
public abstract class Entity {
double maxHealth, health, speed, damage, width, height, locationX, locationY;
Animation standing, walkingLeft, walkingRight, attacking, jumping, current;
long pastTime = 0;
public boolean isReadyToAttack(long delta) {
if(pastTime < 2 * 500) { //multiply by 1000 to get milliseconds
pastTime += delta;
return false;
}else{
pastTime = 0;
return true;
}
}
public void setMaxHealth(double d){
maxHealth = d;
}
public void setHealth(double d){
health = d;
}
public void setSpeed(double x){
speed = x;
}
public void setDamage(double x){
damage = x;
}
public void setLocation(double x, double y){
locationX = x;
locationY = y;
}
public void setSprites(Animation s, Animation r){
standing = s;
walkingRight = r;
}
public void setCurrent(Animation a){
current = a;
}
public double getMaxHealth(){
return maxHealth;
}
public double getHealth(){
return health;
}
public double getSpeed(){
return speed;
}
public double getDamage(){
return damage;
}
public double getLocationX(){
return locationX;
}
public double getLocationY(){
return locationY;
}
public Animation getStanding(){
return standing;
}
public Animation getRight(){
return walkingRight;
}
public Animation getCurrent(){
return current;
}
public abstract double getAttackRange();
}
The entities class is also extended by the Heroes class which is further extended by the Ares class. Ares is one of the six characters in our game and when you start the game you can choose one of the six in the start menu.
Here is the Hero class:
package Game;
import org.newdawn.slick.Animation;
public abstract class Hero extends Entity{
static double gravity = 0.1;
double velocity;
int level;
boolean disabled, grounded = true;
public void setLevel(int i){
level = i;
}
public void setVelocity(double d){
velocity = d;
}
public void setDisabled(boolean b){
disabled = b;
}
public void setGrounded(boolean b){
grounded = b;
}
public int getLevel(){
return level;
}
public double getVelocity(){
return velocity;
}
public boolean getDisabled(){
return disabled;
}
public boolean getGrounded(){
return grounded;
}
public double getGravity(){
return gravity;
}
public boolean isAlive(){
if(getHealth() > 0)
return true;
return false;
}
public void attack(Entity gettingAttacked, Hero attacking) {
gettingAttacked.setHealth(gettingAttacked.getHealth() - attacking.getDamage());
}
public boolean isReadyToRegenerate(long delta) {
if(pastTime < 1 * 1000){
pastTime += delta;
return false;
}
else{
pastTime = 0;
return true;
}
}
public static void regenerate(Hero h, long delta){
if(h.getHealth() >= 0 && h.getHealth() < h.getMaxHealth())
if(h.isReadyToRegenerate(delta))
h.setHealth(h.getHealth() + (h.getMaxHealth()) * 0.01);
}
public abstract void levelUp();
}
Here is the Ares class:
package Game;
import org.newdawn.slick.Animation;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
public class Ares extends Hero {
public Ares() throws SlickException {
setHealth(500);
setSpeed(0.4);
setDamage(70);
setLocation(850,625);
setSprites(new Animation(new SpriteSheet("res/Ares.png", 191, 275), 200), new Animation(new SpriteSheet("res/AresWalk.png", 191, 275), 200));
}
public void levelUp() {
level += 1;
health *= 1.1;
damage *= 1.1;
}
public double getAttackRange() {
return 250;
}
public double maxHealth() {
return 500;
}
}
Here is the Battle code, which contains the main gameplay code, the code is currently uncompleted and only has movement:
package Game;
import java.util.ArrayList;
import org.lwjgl.input.*;
import org.newdawn.slick.*;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.state.*;
public class Battle extends BasicGameState {
private ArrayList<Entity> entities = new ArrayList<Entity>();
private Hero player;
private Image background;
private int midScreen, ground;
public void init(GameContainer gc, StateBasedGame sbg) throws SlickException{
gc.setVSync(true);
player = new Ares();
player.setCurrent(player.getStanding());
entities.add(player);
background = new Image("res/Background.png");
midScreen = 800;
ground = 625;
}
public void render(GameContainer gc, StateBasedGame sbg, Graphics g) throws SlickException{
g.translate((float)-player.getLocationX() + midScreen, (float)-player.getLocationY() + ground);
background.draw(0, -920);
for(int i = 0; i < entities.size(); i++)
entities.get(i).getCurrent().draw((float)entities.get(i).getLocationX(), (float)entities.get(i).getLocationY());
}
public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException {
player.getCurrent().update(delta); // ensures
Input kb = gc.getInput();
if (kb.isKeyDown(Input.KEY_SPACE) && player.getGrounded()) {
player.setGrounded(false);
player.setVelocity(7);
player.setLocation(player.getLocationX(), player.getLocationY() - player.getVelocity());
player.setCurrent(player.getStanding());
}
else if (kb.isKeyDown(Input.KEY_A) && player.getLocationX() > 800) {
player.setLocation(player.getLocationX() - player.getSpeed() * delta, player.getLocationY());
if(player.grounded)
player.setCurrent(player.getRight());
}
else if (kb.isKeyDown(Input.KEY_D) && player.getLocationX() < 8580) {
player.setLocation(player.getLocationX() + player.getSpeed() * delta, player.getLocationY());
if(player.grounded)
player.setCurrent(player.getRight());
}
else
player.setCurrent(player.getStanding());
if (Math.abs(player.getLocationY() - ground) < .01)
player.setGrounded(true);
if (player.getGrounded() == false) {
player.setVelocity(player.getVelocity() - player.getGravity());
player.setLocation(player.getLocationX(), player.getLocationY() - player.getVelocity());
}
//else if (kb.isKeyDown(Input.KEY_A)) //&& (!(aresRect.intersects(turretRect))))
//{
// x -= 0.3 * delta;
//}
//if(kb.isKeyDown(Input.KEY_S))
// y += 0.3 * delta;
//else if (kb.isKeyDown(Input.KEY_W)) //&& (!(aresRect.intersects(turretRect))))
// y -= 0.3 * delta;
}
public int getID(){
return 1;
}
}
What I am having difficulty with is creating a server and client that gets the input from the players, such as location, who chose which player, etc. I'm wondering that if in Battle, when Player objects are made, how would I be able to get location and other specific attributes of the objects and give them to the other computers?
So far, the furthest I have gotten with networking code is a chat server, but I am confused as to how to apply parts of that information into the game to include getting input from a user. I have looked at a bunch of tutorials on creating a multi-player network, but I'm still confused. As for my experience, I've had around 2 years experience coding (only one with Java) but this is my first time working with networking. I experimented with a UDP connection, here is my Server:
package net;
import java.io.*;
import java.net.*;
import java.util.*;
import main.Game;
import packets.*;
import packets.Packet.PacketTypes;
public class GameServer extends Thread{
private DatagramSocket socket;
private Game game;
private List<PlayerMP> connectedPlayers = new ArrayList <PlayerMP>();
public GameServer(Game game){
this.game = game;
try {
socket = new DatagramSocket(7777);
} catch (SocketException e) {
e.printStackTrace();
}
}
public void run(){
while(true){
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
try {
socket.receive(packet);
} catch(IOException e) {
e.printStackTrace();
}
parsePacket(packet.getData(), packet.getAddress(), packet.getPort());
}
}
private void parsePacket(byte[] data, InetAddress address, int port) {
String message = new String(data).trim();
PacketTypes type = Packet.lookupPacket(message.substring(0, 2));
Packet packet = null;
switch(type) {
default :
case INVALID:
break;
case LOGIN:
packet = new Packet00Login(data);
System.out.println("[" + address.getHostAddress() + ":" + port + "] " + ((Packet00Login) packet).getUsername() +
" has connected.");
PlayerMP player = new PlayerMP(address, port);
this.addConnection(player, ((Packet00Login)packet));
this.connectedPlayers.add(player);
break;
case DISCONNECT:
break;
case MOVE:
packet = new Packet02Move(data);
System.out.println(((Packet02Move)packet).getName() +"has moved to" + ((Packet02Move)packet).getX() + "," + ((Packet02Move)packet).getY());
this.handleMove((Packet02Move)packet);
}
}
private void handleMove(Packet02Move packet) {
if (getPlayerMP(packet.getUsername()) != null){
int index = getPlayerMPIndex(getName());
this.connectedPlayers.get(index).locationY = packet.getX();
this.connectedPlayers.get(index).locationX = packet.getX();
packet.writeData(this);
}
}
public void addConnection(PlayerMP player, Packet00Login packet) {
boolean alreadyConnected = false;
for(PlayerMP p : this.connectedPlayers) {
if (player.getUsername().equalsIgnoreCase(p.getUsername())) {
if(p.ipAddress == null){
p.ipAddress = player.ipAddress;
}
if(p.port == -1) {
p.port = player.port;
}
alreadyConnected = true;
} else {
sendData(packet.getData(), p.ipAddress, p.port);
packet = new Packet00Login(p.getUsername());
sendData(packet.getData(), player.ipAddress, player.port);
}
if (alreadyConnected){
this.connectedPlayers.add(player);
packet.writeData(this);
}
}
}
public void sendData(byte[] data, InetAddress ipAddress, int port) {
DatagramPacket packet = new DatagramPacket(data, data.length, ipAddress, port);
try {
socket.send(packet);
} catch(IOException e) {
e.printStackTrace();
}
}
public void sendDataToAllClients(byte[] data) {
for(PlayerMP p : connectedPlayers)
sendData(data, p.ipAddress, p.port);
}
}
I could not figure out how to get much further than sending a message back and forth with a TCP connection:
import java.io.*;
import java.net.*;
public class Server {
private static ServerSocket serverSocket;
private static Socket socket;
private static DataOutputStream out;
private static Users[] user = new Users[6];
private static DataInputStream in;
public static void main(String[] args) throws Exception{
System.out.println("Starting server");
serverSocket = new ServerSocket(0);
System.out.println("Server Started");
while(true){
socket = serverSocket.accept();
for (int i = 0; i < 6; i++){
System.out.println("Connection from: " + socket.getInetAddress());
out = new DataOutputStream(socket.getOutputStream());
in = new DataInputStream(socket.getInputStream());
if (user[i] == null){
user[i] = new Users(out, in, user);
Thread thread = new Thread();
thread.start();
break;
}
}
}
}
}
class Users implements Runnable{
private DataOutputStream out;
private DataInputStream in;
private Users[] user = new Users[6];
public Users(DataOutputStream fromOut, DataInputStream goingIn, Users[] userArr){
out = fromOut;
in = goingIn;
user = userArr;
}
public void run(){
while(true){
try{
String message = in.readUTF();
for (int i = 0; i < 6; i++){
if (user[i] != null)
user[i].out.writeUTF(message);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
I would like to figure this out. I am not even completely sure where to start or whether or not I am on the right track.
Note: I am coding in Java, and trying to use a TCP connection to code this. I am open to UDP if you think it may be easier.
I am hoping to get notifications of note on/off events in a playing MIDI sequence to show the notes on a screen based (piano) keyboard.
The code below adds a MetaEventListener and a ControllerEventListener when playing a MIDI file, but only shows a few messages at the start and end of the track.
How can we listen for note on & note off MIDI events?
import java.io.File;
import javax.sound.midi.*;
import javax.swing.JOptionPane;
class PlayMidi {
public static void main(String[] args) throws Exception {
/* This MIDI file can be found at..
https://drive.google.com/open?id=0B5B9wDXIGw9lR2dGX005anJsT2M&authuser=0
*/
File path = new File("I:\\projects\\EverLove.mid");
Sequence sequence = MidiSystem.getSequence(path);
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
MetaEventListener mel = new MetaEventListener() {
#Override
public void meta(MetaMessage meta) {
final int type = meta.getType();
System.out.println("MEL - type: " + type);
}
};
sequencer.addMetaEventListener(mel);
int[] types = new int[128];
for (int ii = 0; ii < 128; ii++) {
types[ii] = ii;
}
ControllerEventListener cel = new ControllerEventListener() {
#Override
public void controlChange(ShortMessage event) {
int command = event.getCommand();
if (command == ShortMessage.NOTE_ON) {
System.out.println("CEL - note on!");
} else if (command == ShortMessage.NOTE_OFF) {
System.out.println("CEL - note off!");
} else {
System.out.println("CEL - unknown: " + command);
}
}
};
int[] listeningTo = sequencer.addControllerEventListener(cel, types);
for (int ii : listeningTo) {
System.out.println("Listening To: " + ii);
}
sequencer.setSequence(sequence);
sequencer.start();
JOptionPane.showMessageDialog(null, "Exit this dialog to end");
sequencer.stop();
sequencer.close();
}
}
Here is an implementation of the first suggestion of the accepted answer. It will present an option pane confirm dialog as to whether or not to add a new track to hold meta events corresponding to the NOTE_ON & NOTE_OFF messages of each of the existing tracks.
If the user chooses to do that, they'll see Meta events throughout the playback of the MIDI sequence.
import java.io.File;
import javax.sound.midi.*;
import javax.swing.JOptionPane;
class PlayMidi {
/** Iterates the MIDI events of the first track and if they are a
* NOTE_ON or NOTE_OFF message, adds them to the second track as a
* Meta event. */
public static final void addNotesToTrack(
Track track,
Track trk) throws InvalidMidiDataException {
for (int ii = 0; ii < track.size(); ii++) {
MidiEvent me = track.get(ii);
MidiMessage mm = me.getMessage();
if (mm instanceof ShortMessage) {
ShortMessage sm = (ShortMessage) mm;
int command = sm.getCommand();
int com = -1;
if (command == ShortMessage.NOTE_ON) {
com = 1;
} else if (command == ShortMessage.NOTE_OFF) {
com = 2;
}
if (com > 0) {
byte[] b = sm.getMessage();
int l = (b == null ? 0 : b.length);
MetaMessage metaMessage = new MetaMessage(com, b, l);
MidiEvent me2 = new MidiEvent(metaMessage, me.getTick());
trk.add(me2);
}
}
}
}
public static void main(String[] args) throws Exception {
/* This MIDI file can be found at..
https://drive.google.com/open?id=0B5B9wDXIGw9lR2dGX005anJsT2M&authuser=0
*/
File path = new File("I:\\projects\\EverLove.mid");
Sequence sequence = MidiSystem.getSequence(path);
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
MetaEventListener mel = new MetaEventListener() {
#Override
public void meta(MetaMessage meta) {
final int type = meta.getType();
System.out.println("MEL - type: " + type);
}
};
sequencer.addMetaEventListener(mel);
int[] types = new int[128];
for (int ii = 0; ii < 128; ii++) {
types[ii] = ii;
}
ControllerEventListener cel = new ControllerEventListener() {
#Override
public void controlChange(ShortMessage event) {
int command = event.getCommand();
if (command == ShortMessage.NOTE_ON) {
System.out.println("CEL - note on!");
} else if (command == ShortMessage.NOTE_OFF) {
System.out.println("CEL - note off!");
} else {
System.out.println("CEL - unknown: " + command);
}
}
};
int[] listeningTo = sequencer.addControllerEventListener(cel, types);
StringBuilder sb = new StringBuilder();
for (int ii = 0; ii < listeningTo.length; ii++) {
sb.append(ii);
sb.append(", ");
}
System.out.println("Listenning to: " + sb.toString());
int mirror = JOptionPane.showConfirmDialog(
null,
"Add note on/off messages to another track as meta messages?",
"Confirm Mirror",
JOptionPane.OK_CANCEL_OPTION);
if (mirror == JOptionPane.OK_OPTION) {
Track[] tracks = sequence.getTracks();
Track trk = sequence.createTrack();
for (Track track : tracks) {
addNotesToTrack(track, trk);
}
}
sequencer.setSequence(sequence);
sequencer.start();
JOptionPane.showMessageDialog(null, "Exit this dialog to end");
sequencer.stop();
sequencer.close();
}
}
Implementation of keyboard
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.sound.midi.*;
import java.util.ArrayList;
import java.io.*;
import java.net.URL;
public class MidiPianola {
private JComponent ui = null;
public static final int OTHER = -1;
public static final int NOTE_ON = 1;
public static final int NOTE_OFF = 2;
private OctaveComponent[] octaves;
Sequencer sequencer;
int startOctave = 0;
int numOctaves = 0;
MidiPianola(int startOctave, int numOctaves)
throws MidiUnavailableException {
this.startOctave = startOctave;
this.numOctaves = numOctaves;
initUI();
}
public void openMidi(URL url)
throws InvalidMidiDataException, IOException {
openMidi(url.openStream());
}
public void openMidi(InputStream is)
throws InvalidMidiDataException, IOException {
Sequence sequence = MidiSystem.getSequence(is);
Track[] tracks = sequence.getTracks();
Track trk = sequence.createTrack();
for (Track track : tracks) {
addNotesToTrack(track, trk);
}
sequencer.setSequence(sequence);
startMidi();
}
public void startMidi() {
sequencer.start();
}
public void stopMidi() {
sequencer.stop();
}
public void closeSequencer() {
sequencer.close();
}
private void handleNote(final int command, int note) {
OctaveComponent octave = getOctaveForNote(note);
PianoKey key = octave.getKeyForNote(note);
if (command == NOTE_ON) {
key.setPressed(true);
} else if (command == NOTE_OFF) {
key.setPressed(false);
}
ui.repaint();
}
private OctaveComponent getOctaveForNote(int note) {
return octaves[(note / 12) - startOctave];
}
public void initUI() throws MidiUnavailableException {
if (ui != null) {
return;
}
sequencer = MidiSystem.getSequencer();
MetaEventListener mel = new MetaEventListener() {
#Override
public void meta(MetaMessage meta) {
final int type = meta.getType();
byte b = meta.getData()[1];
int i = (int) (b & 0xFF);
handleNote(type, i);
}
};
sequencer.addMetaEventListener(mel);
sequencer.open();
ui = new JPanel(new BorderLayout(4, 4));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
JPanel keyBoard = new JPanel(new GridLayout(1, 0));
ui.add(keyBoard, BorderLayout.CENTER);
int end = startOctave + numOctaves;
octaves = new OctaveComponent[end - startOctave];
for (int i = startOctave; i < end; i++) {
octaves[i - startOctave] = new OctaveComponent(i);
keyBoard.add(octaves[i - startOctave]);
}
JToolBar tools = new JToolBar();
tools.setFloatable(false);
ui.add(tools, BorderLayout.PAGE_START);
tools.setFloatable(false);
Action open = new AbstractAction("Open") {
JFileChooser fileChooser = new JFileChooser();
#Override
public void actionPerformed(ActionEvent e) {
int result = fileChooser.showOpenDialog(ui);
if (result == JFileChooser.APPROVE_OPTION) {
File f = fileChooser.getSelectedFile();
try {
openMidi(f.toURI().toURL());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
};
tools.add(open);
Action rewind = new AbstractAction("Rewind") {
#Override
public void actionPerformed(ActionEvent e) {
sequencer.setTickPosition(0);
}
};
tools.add(rewind);
Action play = new AbstractAction("Play") {
#Override
public void actionPerformed(ActionEvent e) {
startMidi();
}
};
tools.add(play);
Action stop = new AbstractAction("Stop") {
#Override
public void actionPerformed(ActionEvent e) {
stopMidi();
}
};
tools.add(stop);
}
public JComponent getUI() {
return ui;
}
/**
* Iterates the MIDI events of the first track, and if they are a NOTE_ON or
* NOTE_OFF message, adds them to the second track as a Meta event.
*/
public static final void addNotesToTrack(
Track track,
Track trk) throws InvalidMidiDataException {
for (int ii = 0; ii < track.size(); ii++) {
MidiEvent me = track.get(ii);
MidiMessage mm = me.getMessage();
if (mm instanceof ShortMessage) {
ShortMessage sm = (ShortMessage) mm;
int command = sm.getCommand();
int com = OTHER;
if (command == ShortMessage.NOTE_ON) {
com = NOTE_ON;
} else if (command == ShortMessage.NOTE_OFF) {
com = NOTE_OFF;
}
if (com > OTHER) {
byte[] b = sm.getMessage();
int l = (b == null ? 0 : b.length);
MetaMessage metaMessage = new MetaMessage(
com,
b,
l);
MidiEvent me2 = new MidiEvent(metaMessage, me.getTick());
trk.add(me2);
}
}
}
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
SpinnerNumberModel startModel =
new SpinnerNumberModel(2,0,6,1);
JOptionPane.showMessageDialog(
null,
new JSpinner(startModel),
"Start Octave",
JOptionPane.QUESTION_MESSAGE);
SpinnerNumberModel octavesModel =
new SpinnerNumberModel(5,5,11,1);
JOptionPane.showMessageDialog(
null,
new JSpinner(octavesModel),
"Number of Octaves",
JOptionPane.QUESTION_MESSAGE);
final MidiPianola o = new MidiPianola(
startModel.getNumber().intValue(),
octavesModel.getNumber().intValue());
WindowListener closeListener = new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
o.closeSequencer();
}
};
JFrame f = new JFrame("MIDI Pianola");
f.addWindowListener(closeListener);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.setResizable(false);
f.pack();
f.setVisible(true);
} catch (MidiUnavailableException ex) {
ex.printStackTrace();
} catch (InvalidMidiDataException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
};
SwingUtilities.invokeLater(r);
}
}
class OctaveComponent extends JPanel {
int octave;
ArrayList<PianoKey> keys;
PianoKey selectedKey = null;
public OctaveComponent(int octave) {
this.octave = octave;
init();
}
public PianoKey getKeyForNote(int note) {
int number = note % 12;
return keys.get(number);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
for (PianoKey key : keys) {
key.draw(g2);
}
}
public static final Shape
removeArrayFromShape(Shape shape, Shape[] shapes) {
Area a = new Area(shape);
for (Shape sh : shapes) {
a.subtract(new Area(sh));
}
return a;
}
public final Shape getEntireBounds() {
Area a = new Area();
for (PianoKey key : keys) {
a.add(new Area(key.keyShape));
}
return a;
}
#Override
public Dimension getPreferredSize() {
Shape sh = getEntireBounds();
Rectangle r = sh.getBounds();
Dimension d = new Dimension(r.x + r.width, r.y + r.height + 1);
return d;
}
public void init() {
keys = new ArrayList<PianoKey>();
int w = 30;
int h = 200;
int x = 0;
int y = 0;
int xs = w - (w / 3);
Shape[] sharps = new Shape[5];
int hs = h * 3 / 5;
int ws = w * 2 / 3;
sharps[0] = new Rectangle2D.Double(xs, y, ws, hs);
xs += w;
sharps[1] = new Rectangle2D.Double(xs, y, ws, hs);
xs += 2 * w;
sharps[2] = new Rectangle2D.Double(xs, y, ws, hs);
xs += w;
sharps[3] = new Rectangle2D.Double(xs, y, ws, hs);
xs += w;
sharps[4] = new Rectangle2D.Double(xs, y, ws, hs);
Shape[] standards = new Shape[7];
for (int ii = 0; ii < standards.length; ii++) {
Shape shape = new Rectangle2D.Double(x, y, w, h);
x += w;
standards[ii] = removeArrayFromShape(shape, sharps);
}
int note = 0;
int ist = 0;
int ish = 0;
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "C", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "C#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "D", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "D#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "E", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "F", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "F#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "G", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "G#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "A", this));
keys.add(new PianoKey(sharps[ish++], (octave * 12) + note++, "A#", this));
keys.add(new PianoKey(standards[ist++], (octave * 12) + note++, "B", this));
}
}
class PianoKey {
Shape keyShape;
int number;
String name;
Component component;
boolean pressed = false;
PianoKey(Shape keyShape, int number, String name, Component component) {
this.keyShape = keyShape;
this.number = number;
this.name = name;
this.component = component;
}
public void draw(Graphics2D g) {
if (name.endsWith("#")) {
g.setColor(Color.BLACK);
} else {
g.setColor(Color.WHITE);
}
g.fill(keyShape);
g.setColor(Color.GRAY);
g.draw(keyShape);
if (pressed) {
Rectangle r = keyShape.getBounds();
GradientPaint gp = new GradientPaint(
r.x,
r.y,
new Color(255, 225, 0, 40),
r.x,
r.y + (int) r.getHeight(),
new Color(255, 225, 0, 188));
g.setPaint(gp);
g.fill(keyShape);
g.setColor(Color.GRAY);
g.draw(keyShape);
}
}
public boolean isPressed() {
return pressed;
}
public void setPressed(boolean pressed) {
this.pressed = pressed;
}
}
I'll be watching to see if there is a better answer than either of the two suggestions that I have, which are clearly less than ideal.
edit the midi file to include meta events that match the existing key on/off
write you own event system
I don't do a lot yet with MIDI, myself. I only occasionally import MIDI scores and strip out most of the info, converting it to use with an event-system I wrote for my own audio needs (triggering an FM synthesizer I wrote).
An answer here. Something like:
class MidiPlayer implements Receiver {
private Receiver myReceiver;
void play() {
Sequencer sequencer = MidiSystem.getSequencer();
...
// Save the original receiver
this.myReceiver = sequencer.getReceiver();
// Override the receiver
sequencer.getTransmitter().setReceiver(this);
sequencer.start();
}
#Override
public void send(MidiMessage msg, long tstamp) {
// Send the message to the original receiver
this.myReceiver.send(msg, tstamp);
// Process the message
if (msg instanceof ShortMessage) {
ShortMessage shortMsg = (ShortMessage) msg;
if (shortMsg.getCommand() == ShortMessage.NOTE_ON) {
System.out.printf("NOTE ON\n");
}
}
...
}