Reputation: 23
I'm writing a rhythm game in Java; right now I've reached the point where I'm trying to implement a metronome object.
I've coded a data structure that stores 8 channels of music data into a single QuarterBeat object; these are in turn stored in groups of 64 to make 4-measure 'chunk' objects.
To keep things synchronized properly I wanted to use a pair of parallel threads: one runs through the various events that happen at every quarter-beat before stopping on a 'wait()' method, while the other waits an amount of time derived from the BPM before signaling the first.
This is the code for the thread doing the work.
public class InGame {
public static boolean gameRunning = false;
public static boolean holdChunk = false;
public static boolean waiting = false;
public static ArrayList<Player> players = new ArrayList<Player>();
public void startUp() throws InterruptedException{
Parser.loadSamples();
for (int p = 0; p < Player.voicePool.size(); p++) {
Player makePlay = new Player();
makePlay.setChannel(p);
players.add(makePlay);
}
LevelStructure.SongBuild();
Metro timer = new Metro();
gamePlay(timer);
gameEnd();
}
synchronized public void cycle(Metro timer) throws InterruptedException{
int endPoint = LevelStructure.getChunkTotal();
for (int chunk = 0; chunk < endPoint; chunk++){
LevelStructure.setActiveChunk(chunk);
for (int quartBeat = 0; quartBeat < 64; quartBeat++){
synchronized (this){
new Thread(timer.ticking(this));
Player.getNewNotes(LevelStructure.getQuartBeat(quartBeat));
players.get(0).playback(LevelStructure.getQuartBeat(quartBeat));
waiting = true;
while (waiting) {
wait();
}
}
}
if (holdChunk) chunk--;
}
}
}
And the code for the Metro object:
public class Metro {
public static int BPM;
synchronized public Runnable ticking(InGame parent) throws InterruptedException{
synchronized (parent) {
Thread.sleep(15000/BPM);
InGame.waiting = false;
parent.notifyAll();
}
return null;
}
}
Right now it's throwing an Illegal Monitor State exception every time I try to run it; I've tried researching proper implementation of wait()/notify() on my own, but I'm still fairly new to Java and I can't find an explanation of handling parallel threading that I can understand. Do I need to invoke both the Cycle and Metro threads from a parent process?
Edit: Updated code: the issue now is that, instead of actually running in parallel, the Cycle object waits for the timer.ticking method to execute, then performs the actions it's supposed to do while Metro is sleeping, then gets stuck waiting for a notify that will never come. Which means that the threads aren't actually executing parallel to one-another.
Upvotes: 0
Views: 154
Reputation: 269627
Kennedy's answer gives the solution to the question you asked about the exception, but the bigger problem is that you are using wait()
/notify()
with only one thread. The thread that is supposed to call playback()
is the one that executes ticking()
, so it will pause for a quarter beat. I'd do the loop like to try to stay on schedule:
void cycle()
throws InterruptedException
{
int endPoint = LevelStructure.getChunkTotal();
for (int chunk = 0; chunk < endPoint; chunk++) {
LevelStructure.setActiveChunk(chunk);
for (int quartBeat = 0; quartBeat < 64; quartBeat++) {
long start = System.nanoTime();
Player.playback(LevelStructure.getQuartBeat(quartBeat));
long delay = 15_000_000_000L / BPM - (System.nanoTime() - start);
if (delay < 0)
throw new IllegalStateException("Allotted time exceeded");
if (delay > 0)
Thread.sleep(delay / 1_000_000, delay % 1_000_000);
}
}
}
If you feel like you need multiple threads, here's a more complicated approach. At its heart, it relies on a CyclicBarrier
to coordinate between the worker and the timer. You could also schedule messages to the synthesizer using a ScheduledExecutorService
.
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
class Test
{
private final CyclicBarrier sync = new CyclicBarrier(2);
/** Duration of quarter beat in milliseconds. */
private final int qb;
Test(int bpm)
{
qb = 15000 / bpm;
}
private void cycle()
throws InterruptedException, BrokenBarrierException
{
Timer timer = new Timer();
timer.start();
sync.await();
for (int chunk = 0; chunk < 4; ++chunk)
chunk();
timer.interrupt();
}
private void chunk()
throws InterruptedException, BrokenBarrierException
{
long t1 = System.nanoTime();
for (int count = 0; count < 64; ++count) {
playback();
long t2 = System.nanoTime();
sync.await(); /* Now wait the remaining time, if any. */
long t3 = System.nanoTime();
float t = TimeUnit.NANOSECONDS.toMillis(t2 - t1) / 1000F;
float c = TimeUnit.NANOSECONDS.toMillis(t3 - t1) / 1000F;
System.out.printf("Task: %5.3f, Cycle: %5.3f seconds%n", t, c);
t1 = t3;
}
}
void playback()
{
/* Simulate performing some work sleeping a random time which is,
* on average, an eighth of a beat, but can "jank" occasionally by taking
* too long to do its job. */
long delay = (long) (-qb / 2 * Math.log(1 - Math.random()));
// long delay = qb / 2; /* Simulate a predictable workload. */
try {
Thread.sleep(delay);
}
catch (InterruptedException abort) {
Thread.currentThread().interrupt();
}
}
private final class Timer
extends Thread
{
@Override
public void run()
{
try {
sync.await();
while (true) {
Thread.sleep(qb);
sync.await();
}
}
catch (InterruptedException abort) {
return;
}
catch (BrokenBarrierException ex) {
ex.printStackTrace();
}
}
};
public static void main(String... argv)
throws Exception
{
Test player = new Test(120);
long t0 = System.nanoTime();
player.cycle();
System.out.printf("Total: %5.3f seconds%n", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t0) / 1000F);
}
}
Upvotes: 1
Reputation: 2251
I guess it's because you are calling this.wait()
without having the lock of the object this
.
Try to get the lock first.
synchronized (this) {
this.wait();
}
Upvotes: 1