Skip to content

Commit

Permalink
Successfully introduced threading, separating tics from the draw cycl…
Browse files Browse the repository at this point in the history
…e. Switched from JAVA2D to P2D, which is MUCH faster.
  • Loading branch information
Tiger Mou committed Dec 1, 2017
1 parent fdc6e96 commit fac7d2e
Show file tree
Hide file tree
Showing 16 changed files with 522 additions and 406 deletions.
15 changes: 10 additions & 5 deletions GuitarHeroVisualization/.classpath
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src" />
<classpathentry kind="con"
path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8" />
<classpathentry kind="lib" path="libextern/core.jar" />
<classpathentry kind="output" path="bin" />
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="lib" path="libextern/core.jar"/>
<classpathentry kind="lib" path="libextern/jogl-all.jar"/>
<classpathentry kind="lib" path="libextern/jogl-all-natives-windows-amd64.jar"/>
<classpathentry kind="lib" path="libextern/jogl-all-natives-windows-i586.jar"/>
<classpathentry kind="lib" path="libextern/gluegen-rt-natives-windows-amd64.jar"/>
<classpathentry kind="lib" path="libextern/gluegen-rt-natives-windows-i586.jar"/>
<classpathentry kind="lib" path="libextern/gluegen-rt.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
158 changes: 95 additions & 63 deletions GuitarHeroVisualization/src/main/java/GuitarHeroVisual.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
package main.java;

import java.io.File;
import java.util.TreeSet;

import processing.core.PApplet;
import processing.event.MouseEvent;
import reusable.events.EventQueue;
import reusable.events.LoadSongRunnable;
import reusable.events.PlayPauseRunnable;
import reusable.guitar.GuitarString;
import reusable.keymap.AllStringsRunner;
import reusable.keymap.KeyMap;
import reusable.keymap.StringRunner;
import reusable.menu.ButtonController;
import reusable.menu.MenuSingleton;
import reusable.menu.Menu;
import reusable.menu.RunnableButtonController;

/**
* Main Processing applet. Visualizes a guitar string
Expand All @@ -31,7 +34,7 @@ public static void main(String[] args) {

/** Pre-setup settings */
public void settings() {
size(1300, 700);
size(1300, 700, P2D);
}

/** Set up the applet. Initialize vars here? */
Expand All @@ -40,7 +43,7 @@ public void setup() {
stroke(0);
StringManager.setInstance(this);
for (int i = 0; i < NUM_STRINGS; i++) {
float factor = (float) Math.pow(2, i / 12.0);
double factor = Math.pow(2, i / 12.0);
StringManager.getInstance().addString(BASE_FREQ * factor);
}

Expand All @@ -52,106 +55,135 @@ public void setup() {
keyMap.addRunnable(keys[i], new StringRunner(i));
}

TreeSet<Integer> chordA = new TreeSet<Integer>();
chordA.add(0);
chordA.add(4);
chordA.add(7);
keyMap.addRunnable('z', new StringRunner(chordA));

MenuSingleton.setInstance(this);
MenuSingleton.getInstance().addController("Button1", new ButtonController(this, "Button1"));

//EventQueue.getInstance().addEvent(2, new StringRunner(chordA));
//EventQueue.getInstance().beginEvents();

keyMap.addRunnable('z', new StringRunner(0, 4, 7));

selectInput("Select a file to process:", "loadSongFile");
Menu.setInstance(this);
Menu myMenu = Menu.getInstance();
myMenu.addController(
"PlayPauseEvents",
new RunnableButtonController(this, "Play/Pause", new PlayPauseRunnable()));
myMenu.addController(
"LoadSong0", new RunnableButtonController(this, "Load Song", new LoadSongRunnable(this)));
myMenu.addController("Button0", new ButtonController(this, "Button0"));

//EventQueue.getInstance().addEvent(2, new StringRunner(chordA));
//EventQueue.getInstance().beginEvents();
if (USE_THREADS) {
thread("threadedTic");
lastThreadTic = System.currentTimeMillis();
}
}

/**
* Callback method for selecting an input
*
* @param selection
*/
public static void loadSongFile(File selection) {
if (selection == null) {
println("Window was closed or the user hit cancel.");
} else {
println("User selected " + selection.getAbsolutePath());
EventQueue.getInstance().loadStringEventsFile(selection.getAbsolutePath());
}
}
public void loadSongFile(File selection) {
if (selection == null) {
println("Window was closed or the user hit cancel.");
} else {
println("User selected " + selection.getAbsolutePath());
EventQueue.getInstance().loadStringEventsFile(selection);
}
}

/** Main draw loop. Target fps is 60, but 30 is also fine. */
public void draw() {
background(255);
//Process menu data at the beginning
MenuSingleton menuSing = MenuSingleton.getInstance();
if((boolean) menuSing.getControllerValue("Button1")){
KeyMap.getInstance().run('q');
Menu menuSing = Menu.getInstance();
if ((boolean) menuSing.getControllerValue("Button0")) {
KeyMap.getInstance().run('q');
}

pushMatrix();
translate(0, verticalScroll);

pushMatrix();
//Enable scrolling
translate(0, verticalScroll);
StringManager.getInstance().draw();

popMatrix();
//44100 / 60fps = 735 tics per frame for real time
//Play 2x (slightly faster) to fill the StdAudio buffer more quickly and prevent the weird pauses???
//Or maybe just fast enough (since its not always 60fps) to deliver 44100 samples/sec

if (!pauseTic) {
StringManager.getInstance().playStringsLive();
if (!USE_THREADS) StringManager.getInstance().playStringsLive();
}
//if (frameCount % 10 == 0) System.out.println(frameRate);

MenuSingleton.getInstance().draw();

//Draw menu after
Menu.getInstance().draw();
}

/**
* Thread that runs the tic, play and check events via StringManager. Since each tic happens every
* 0.022675737 milliseconds, I'll need to set an integer delay and do multiple tics per delay
*/
public void threadedTic() {
while (!pauseTic) {

long diff = System.currentTimeMillis() - lastThreadTic;
lastThreadTic += diff;
int ticCount = (int) ((diff) * (GuitarString.SAMPLE_RATE / 1000.0));
//For 60 FPS ideal speed, should be approx 3000 tic count.
//Introduce a ticCountMax in case there is a CPU bottleneck
if (ticCount > 10000) ticCount = 10000;
for (int i = 0; i < ticCount; ++i) {
StringManager.getInstance().ticPlayEvents();
}
delay(THREAD_TIC_DELAY);
}
}

/** Called when a key gets pressed */
public void keyPressed() {
if (key == ' ') {
pauseTic = !pauseTic;
} if(key == '.'){
EventQueue.getInstance().beginEvents();
}else if (key == '\n') {
EventQueue.getInstance().togglePause();
}
if (key == '.') {
EventQueue.getInstance().beginEvents();
} else if (key == '\n') {
if (pauseTic) { //only let this run if the display is paused
StringManager.getInstance().ticAllOneCycle();
}
} else KeyMap.getInstance().run(key);
}

/**
* Handle mouse presses
*/
public void mousePressed(){
MenuSingleton.getInstance().pressUpdate(mouseX, mouseY);

/** Handle mouse presses */
public void mousePressed() {
Menu.getInstance().pressUpdate(mouseX, mouseY);
}

/**
* Handle mouse releases
*/
public void mouseReleased(){
MenuSingleton.getInstance().releaseUpdate(mouseX, mouseY);

/** Handle mouse releases */
public void mouseReleased() {
Menu.getInstance().releaseUpdate(mouseX, mouseY);
}


/** Handle mouse wheel scrolling */
public void mouseWheel(MouseEvent event) {
float e = event.getCount();
verticalScroll -= 10*e;
if(verticalScroll >= MAX_SCROLL) verticalScroll = MAX_SCROLL;
else if(verticalScroll <= MIN_SCROLL) verticalScroll = MIN_SCROLL;
}
float e = event.getCount();
verticalScroll -= 10 * e;
if (verticalScroll >= MAX_SCROLL) verticalScroll = MAX_SCROLL;
else if (verticalScroll <= MIN_SCROLL) verticalScroll = MIN_SCROLL;
}

//Pauses the execution of tics
private boolean pauseTic = false;

//Base frequency for strings higher frequencies results in faster displaying
private final float BASE_FREQ = 110.0F;
private final double BASE_FREQ = 110.0F;

//13 notes = full scale
private final int NUM_STRINGS = 37;


//Scroll bounds
private final int MAX_SCROLL = 0;
private float verticalScroll = 0;
private final int MIN_SCROLL = -42 * (NUM_STRINGS+1);
private final int MIN_SCROLL = -42 * (NUM_STRINGS + 1);

//Target delay. Due to threading, actual delay may not be accurate.
private long lastThreadTic = 0;
private final int THREAD_TIC_DELAY = 1;

//The problem with threads is that threading prioritizes fps. Solution: P2D give much better fps
private final boolean USE_THREADS = true;
}
23 changes: 13 additions & 10 deletions GuitarHeroVisualization/src/main/java/StringComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class StringComponent {
* @param xStepDist Horizontal distance between each point of the string data
* @param yHeight Vertical span of the string visualization
*/
public StringComponent(PApplet p, float frequency, String label, float xStepDist, float yHeight) {
public StringComponent(PApplet p, double frequency, String label, float xStepDist, float yHeight) {
this.label = label;
this.xStepDist = xStepDist;
this.yHeight = yHeight;
Expand All @@ -46,16 +46,19 @@ public void draw() {
//TODO REMOVE LOCAL VARIABLE(S)
int LABEL_OFFSET = 80;
parent.text(label, 0, 0);

ListIterator<Float> it = mString.getIterator();
float pY = it.next() * yHeight; //initialize first y value to prev

//draw all the points
for (float x = xStepDist + LABEL_OFFSET; it.hasNext(); x += xStepDist) {
float y = it.next() * yHeight; //increments the iterator
parent.line(x - xStepDist, pY, x, y);
pY = y;

synchronized (mString) {
ListIterator<Float> it = mString.getIterator();
float pY = it.next() * yHeight; //initialize first y value to prev

//draw all the points
for (float x = xStepDist + LABEL_OFFSET; it.hasNext(); x += xStepDist) {
float y = it.next() * yHeight; //increments the iterator
parent.line(x - xStepDist, pY, x, y);
pY = y;
}
}

}
}

Expand Down
13 changes: 10 additions & 3 deletions GuitarHeroVisualization/src/main/java/StringManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public void draw() {
*
* @param frequency of the string
*/
public void addString(float frequency) {
public void addString(double frequency) {
float roundedLabelFreq = Math.round(frequency * 10) / 10.0F;
String label = strings.size() + " [" + roundedLabelFreq + "]";
float xStep = 5;
Expand Down Expand Up @@ -136,10 +136,17 @@ public void ticAllOneCycle() {
public void playStringsLive() {
float boundedFRate = parent.frameRate < MIN_FRAME_RATE ? MIN_FRAME_RATE : parent.frameRate;
for (int i = 0; i < GuitarString.SAMPLE_RATE / boundedFRate; ++i) {
EventQueue.getInstance().playEventsTic(GuitarString.TIME_STEP);
ticPlayAll();
ticPlayEvents();
}
}

/**
* Tic each string once and play the sound, increment the time and check events
*/
public void ticPlayEvents(){
EventQueue.getInstance().playEventsTic(GuitarString.TIME_STEP);
ticPlayAll();
}

/** Reset the instance to the initial state. Releases the parent object. */
public void reset() {
Expand Down
Loading

0 comments on commit fac7d2e

Please sign in to comment.