Ryan Foster
Ryan Foster

Reputation: 101

Updating the Application Thread in JavaFx to Draw Nodes

I'm developing a program that will be able to draw the audio waveform using the streamed amplitude data from a microphone or Line-in. The way I thought to do this would be to draw each point from the sample data at a rate equal to the sample rate, going 1 step over in the x direction with each point drawn. Therefore I would need to update the JavaFx Application thread around 44100 times a second to draw each point. Before I start doing this I wanted to test my idea out by just drawing a straight line and only updating each point every half a second. I'm using the Timeline class to do this. My code looks like this:

public class JavaFxPractice extends Application { 
  private int xValue = 50;

  @Override 
  public void start(Stage primaryStage) {      
    Pane pane = new Pane();

    EventHandler<ActionEvent> eventHandler = e -> {
      xValue++;
      Line point = new Line(xValue,50,xValue,50);
      pane.getChildren().add(point);
    };

    Timeline animation = new Timeline(new KeyFrame(Duration.millis((500)), eventHandler)); 
    animation.setCycleCount(500);
    animation.play();

    Scene scene = new Scene(pane, 600, 500);
    primaryStage.setTitle("Streaming Test");
    primaryStage.setScene(scene);
    primaryStage.show();  
  } 
}

However every time I do this my program becomes unresponsive and I have to force close it. I noticed that if I do the same thing but instead make Text blink on and off, it works perfectly fine. Is there a reason Lines aren't able to be drawn using the Timeline class? Does it put too much of a load on the thread? If so, what ways can I go around solving my idea. I just want to be able to draw a waveform in real time, updating at around 44,100 times a second.

Upvotes: 2

Views: 129

Answers (1)

Phil Freihofner
Phil Freihofner

Reputation: 7910

I recommend AnimationTimer for any ongoing animation. It attempts to update as close to 60 fps as it can. (I just read some comments following the first one, recommending AnimationTimer. These folks are right. IDK why they didn't post that as an answer themselves.)

The question then becomes what to display. If I were attacking this problem, here is what I would try:

  1. make an array to hold positions, one bucket per pixel, for the size of the display.
  2. make a function that draws the data from such an array, that can be called by the animation timer
  3. make a concurrent-safe queue to hold these arrays (e.g., ConcurrentLinkedQueue)
  4. load the ConcurrentLinkedQueue with reads from your AudioInputLine
  5. poll from the ConcurrentLinkedQueue from the AnimationTimer

To get the timings to work out, you may need to use decimation (e.g., throwing out every 2nd or 2 out of 3 or more PCM data points), or linear interpolation if the needed decimation doesn't come out to an easy-to-use rational fraction. In other words, you are not tied to a 1:1 correspondence between the array (tied to pixels) and the PCM data points. The more decimation you use, the more high frequencies will be lost.

Upvotes: 0

Related Questions