我有一个使用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);
}
}
答案 0 :(得分:1)
基于Timeline
的节拍器样本,带有一些视觉控制和节拍器节拍定时指示器。
对不起它的一堆代码。你可以通过不为每个概念设置单独的类并且只是内联所有内容来使其更简洁,但是我发现一旦事情开始变得有点不重要(在本例中),就更好地定义单独的对象。
Metronome类通过可观察属性生成节拍,其他类使用侦听器对节拍作出反应。
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;
}
}