我有一个使用JavaFX的简单合成器程序。我正在尝试创建一个名为Metronome的新类来与MainController类进行交互。我需要节拍器在其自己的线程上运行,但仍然能够从MainController运行方法,特别是在每个节拍上。例如,当节拍器打开时,它需要(在自己的线程上)设置形状的填充颜色并通过MainController中的方法发出声音。它将不断地改变颜色(关闭和打开),并在延迟循环中发出声音,直到MainController中的方法停止循环为止。如何让Metronome类与MainController通讯并返回,同时使其在自己的线程上运行?
编辑:所以我基本上需要节拍器类中的run()方法才能运行MainController类中的方法。
public class MainController implements Initializable {
public AudioMain audio = new AudioMain();
@ FXML public AnchorPane mainPane;
public boolean debugMessages = true;
public boolean debugMessages2 = false;
public boolean debugMessages3 = false;
//public final int numKeys = 13;
public int C4 = 60; //The midi pitch of C4
public int octave = 4; //The default octave to be assigned
public String synthType = "Saw";
public SynthSet synth = new SynthSet(111, synthType); //Creates a new SynthSet of 13 SineWaves
public double bpm = 120;
public Metronome metronome = new Metronome(bpm);
....some code later....
public void toggleMetronome() {
metronome.toggleMet();
}
public void lightOn() {
metronomeLight.setFill(lightOnColor);
}
public void lightOff() {
metronomeLight.setFill(lightOffColor);
}
接着..
public class Metronome implements Runnable{
public boolean metronomeOn = false;
public boolean metronomeSound = true;
public boolean metOutputMessages = true;
public boolean tick8th = false;
public double bpm = 20;
public long msPerBeat = (long) (60000 / bpm); // Miliseconds per beat
public int tickCount = 0;
public long nano;
public Metronome() {
}
public Metronome(double beat) {
setTempo(beat);
}
public static void main(String args[]) {
Metronome met = new Metronome();
met.metOn();
}
@Override
public void run() {
System.out.println(msPerBeat);
while (metronomeOn) {
beat();
delay(msPerBeat/2);
if (tick8th) beat8th();
delay(msPerBeat/2);
}
}
public void metOn() {
if (!metronomeOn) {
outMessage("Starting metronome at " + bpm + " bpm");
metronomeOn = true;
new Thread(this).start();
}
}
public void metOff() {
if (metronomeOn) {
outMessage("Stopping metronome");
metronomeOn = false;
}
}
public void toggleMet() {
if (metronomeOn) {
metOff();
}else if (!metronomeOn)
metOn();
}
public void beat() {
tickCount++;
outMessage("Beep " + tickCount);
}
}
最佳答案
基于Timeline
的节拍器的示例,带有一些视觉控件和节拍器节拍定时指示器。
对不起,一堆代码。您可以通过不为每个概念使用单独的类并仅对所有内容进行内联来使其更加简洁,但是我发现,一旦事情开始变得有些琐碎,就可以更好地定义单独的对象(在这种情况下)。
节拍器类通过可观察的属性生成节拍,其他类使用侦听器对节拍做出反应。
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.layout.*;
import javafx.scene.media.AudioClip;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class MetroGnome extends Application {
public void start(Stage stage) {
Metronome metronome = new Metronome();
TempoControl tempoControl = new TempoControl(metronome);
BeatIndicator beatIndicator = new BeatIndicator(metronome);
PlayControl playControl = new PlayControl(metronome);
HBox layout = new HBox(10, playControl, tempoControl, beatIndicator);
layout.setAlignment(Pos.CENTER);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class PlayControl extends ToggleButton {
public PlayControl(Metronome metronome) {
super("Start");
setOnAction(event -> {
if (isSelected()) {
metronome.start();
setText("Stop");
} else {
metronome.stop();
setText("Start");
}
});
}
}
class TempoControl extends VBox {
private static final int MIN_TEMPO = 20;
private static final int MAX_TEMPO = 240;
private static final int DEFAULT_TEMPO = 120;
private Slider tempoSlider = new Slider(MIN_TEMPO, MAX_TEMPO, DEFAULT_TEMPO);
private Label tempoLabel = new Label(tempoSlider.getValue() + "");
public TempoControl(Metronome metronome) {
super(5);
tempoLabel.textProperty().bind(Bindings.format("%.0f", tempoSlider.valueProperty()));
setAlignment(Pos.CENTER);
getChildren().setAll(tempoLabel, tempoSlider);
metronome.setTempo(tempoSlider.getValue());
metronome.tempoProperty().bind(tempoSlider.valueProperty());
}
public DoubleProperty valueProperty() {
return tempoSlider.valueProperty();
}
}
class BeatIndicator extends Circle {
// Ting sound from: http://soundbible.com/1628-Ting.html
private static final String TING_SOUND = "Ting-Popup_Pixels-349896185.wav";
private static AudioClip ting = new AudioClip(
BeatIndicator.class.getResource(TING_SOUND).toExternalForm()
);
public BeatIndicator(Metronome metronome) {
super(10, Color.RED);
ChangeListener<Beat> beatChangeListener = (observable, oldValue, newValue) -> {
ting.play();
setFill(newValue.getTickTock() == 0 ? Color.GREEN : Color.ORANGE);
};
DropShadow dropShadow = new DropShadow(5, (Color) getFill());
fillProperty().addListener((observable, oldValue, newValue) ->
dropShadow.setColor((Color) newValue)
);
Glow beatEffect = new Glow();
beatEffect.setInput(dropShadow);
metronome.isRunningProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
setFill(Color.GREEN);
setEffect(beatEffect);
metronome.beatProperty().addListener(beatChangeListener);
} else {
metronome.beatProperty().removeListener(beatChangeListener);
setFill(Color.RED);
setEffect(null);
}
});
}
}
class Metronome {
private final double DEFAULT_TEMPO = 60;
private ReadOnlyObjectWrapper<Beat> beat = new ReadOnlyObjectWrapper<>(null);
private Timeline timeline = new Timeline();
// tempo is measured in beats per minute.
private DoubleProperty tempo = new SimpleDoubleProperty(DEFAULT_TEMPO);
private ReadOnlyBooleanWrapper isRunning = new ReadOnlyBooleanWrapper(false);
private int tickTock = 0;
public Metronome() {
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.seconds(0), event -> {
beat.set(new Beat(tickTock, timeline.getCurrentTime()));
tickTock = (tickTock + 1) % 2;
}),
new KeyFrame(
Duration.seconds(1)
)
);
tempo.addListener((observable, oldValue, newValue) ->
timeline.setRate(newValue.doubleValue() / 60.0)
);
timeline.setRate(tempo.getValue() / 60.0);
timeline.setCycleCount(Timeline.INDEFINITE);
}
public void start() {
tickTock = 0;
isRunning.set(true);
timeline.playFromStart();
}
public void stop() {
timeline.stop();
isRunning.set(false);
}
public double getTempo() {
return tempo.get();
}
public DoubleProperty tempoProperty() {
return tempo;
}
public void setTempo(double tempo) {
this.tempo.set(tempo);
}
public ReadOnlyObjectProperty<Beat> beatProperty() {
return beat.getReadOnlyProperty();
}
public ReadOnlyBooleanProperty isRunningProperty() {
return isRunning.getReadOnlyProperty();
}
}
class Beat {
private final Duration currentTime;
// tickTock varies switches from one to zero on alternate generated beats.
private final int tickTock;
public Beat(int tickTock, Duration currentTime) {
this.currentTime = currentTime;
this.tickTock = tickTock;
}
public int getTickTock() {
return tickTock;
}
public Duration getCurrentTime() {
return currentTime;
}
}
关于java - Java:如何使单独的类与线程上的主 Controller 通信,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42683059/