kopia lustrzana https://github.com/jameshball/osci-render
Add MIDI keyboard support for playing notes
rodzic
a9f688aa41
commit
5415a91bf3
|
@ -14,7 +14,9 @@ public interface AudioPlayer<S> extends Runnable {
|
|||
|
||||
boolean isPlaying();
|
||||
|
||||
void setFrequency(double quality);
|
||||
void setFrequency(double frequency);
|
||||
|
||||
double getFrequency();
|
||||
|
||||
void addFrame(S frame);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
private static final boolean BIG_ENDIAN = false;
|
||||
// Stereo audio
|
||||
private static final int NUM_OUTPUTS = 2;
|
||||
private static final double MIN_LENGTH_INCREMENT = 0.0001;
|
||||
private static final double MIN_LENGTH_INCREMENT = 0.0000000001;
|
||||
|
||||
private final Callable<AudioEngine> audioEngineBuilder;
|
||||
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
|
||||
|
@ -74,7 +74,7 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
if (currentShape >= frame.size()) {
|
||||
currentShape = 0;
|
||||
frame = frameQueue.take();
|
||||
updateTotalAudioFrames();
|
||||
updateLengthIncrement();
|
||||
}
|
||||
|
||||
return channels;
|
||||
|
@ -131,6 +131,12 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
@Override
|
||||
public void setFrequency(double frequency) {
|
||||
this.frequency = frequency;
|
||||
updateLengthIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getFrequency() {
|
||||
return frequency;
|
||||
}
|
||||
|
||||
private Shape getCurrentShape() {
|
||||
|
@ -141,7 +147,7 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
return frame.get(currentShape);
|
||||
}
|
||||
|
||||
private void updateTotalAudioFrames() {
|
||||
private void updateLengthIncrement() {
|
||||
double totalLength = Shape.totalLength(frame);
|
||||
int sampleRate = device.sampleRate();
|
||||
lengthIncrement = Math.max(totalLength / (sampleRate / frequency), MIN_LENGTH_INCREMENT);
|
||||
|
@ -151,7 +157,7 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
public void run() {
|
||||
try {
|
||||
frame = frameQueue.take();
|
||||
updateTotalAudioFrames();
|
||||
updateLengthIncrement();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Initial frame not found. Cannot continue.");
|
||||
}
|
||||
|
|
|
@ -21,6 +21,15 @@ public class MidiNote {
|
|||
return key;
|
||||
}
|
||||
|
||||
public double frequency() {
|
||||
// Concert A Pitch is A4 and has the key number 69
|
||||
final int KEY_A4 = 69;
|
||||
// 440Hz
|
||||
final int A4 = 440;
|
||||
// Returns the frequency of the given key (equal temperament)
|
||||
return (float) (440 * Math.pow(2, (key - KEY_A4) / 12d));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package sh.ball.gui;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.animation.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.paint.Color;
|
||||
|
@ -49,10 +49,10 @@ import sh.ball.parser.ParserFactory;
|
|||
import sh.ball.shapes.Shape;
|
||||
import sh.ball.shapes.Vector2;
|
||||
|
||||
public class Controller implements Initializable, FrequencyListener, MidiListener, Listener {
|
||||
public class Controller implements Initializable, FrequencyListener, MidiListener, Listener, WritableValue<Double> {
|
||||
|
||||
private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj");
|
||||
private static final double MAX_FREQUENCY = 20000;
|
||||
private static final double MAX_FREQUENCY = 12000;
|
||||
|
||||
private final FileChooser fileChooser = new FileChooser();
|
||||
private final DirectoryChooser folderChooser = new DirectoryChooser();
|
||||
|
@ -71,6 +71,7 @@ public class Controller implements Initializable, FrequencyListener, MidiListene
|
|||
private final AudioDevice defaultDevice;
|
||||
private boolean recording = false;
|
||||
private Timeline recordingTimeline;
|
||||
private Timeline volumeTimeline;
|
||||
private Paint armedMidiPaint;
|
||||
private SVGPath armedMidi;
|
||||
|
||||
|
@ -172,6 +173,7 @@ public class Controller implements Initializable, FrequencyListener, MidiListene
|
|||
@FXML
|
||||
private ComboBox<AudioDevice> deviceComboBox;
|
||||
|
||||
|
||||
public Controller(AudioPlayer<List<Shape>> audioPlayer) throws IOException {
|
||||
this.audioPlayer = audioPlayer;
|
||||
FrameSet<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse();
|
||||
|
@ -617,26 +619,63 @@ public class Controller implements Initializable, FrequencyListener, MidiListene
|
|||
return (double) tmp / factor;
|
||||
}
|
||||
|
||||
private double midiPressureToPressure(Slider slider, int midiPressure) {
|
||||
double max = slider.getMax();
|
||||
double min = slider.getMin();
|
||||
double range = max - min;
|
||||
return min + (midiPressure / 127.0) * range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMidiMessage(int status, MidiNote note, int pressure) {
|
||||
if (armedMidi != null) {
|
||||
if (midiMap.containsValue(armedMidi)) {
|
||||
midiMap.values().remove(armedMidi);
|
||||
public void sendMidiMessage(int status, MidiNote note, int midiPressure) {
|
||||
double frequency = note.frequency();
|
||||
if (frequency > 32 && frequency < 8000) {
|
||||
double scale = midiPressureToPressure(scaleSlider, midiPressure);
|
||||
scale /= 10;
|
||||
|
||||
if (midiPressure == 0) {
|
||||
KeyValue kv = new KeyValue(scaleSlider.valueProperty(), 0, Interpolator.EASE_OUT);
|
||||
KeyFrame kf = new KeyFrame(Duration.millis(500), kv);
|
||||
volumeTimeline = new Timeline(kf);
|
||||
|
||||
Platform.runLater(volumeTimeline::play);
|
||||
} else {
|
||||
if (volumeTimeline != null) {
|
||||
volumeTimeline.stop();
|
||||
}
|
||||
frequencySlider.setValue(Math.log(frequency) / Math.log(MAX_FREQUENCY));
|
||||
audioPlayer.setFrequency(frequency);
|
||||
scaleSlider.setValue(scale);
|
||||
}
|
||||
} else {
|
||||
if (armedMidi != null) {
|
||||
if (midiMap.containsValue(armedMidi)) {
|
||||
midiMap.values().remove(armedMidi);
|
||||
}
|
||||
if (midiMap.containsKey(note)) {
|
||||
midiMap.get(note).setFill(Color.color(1, 1, 1));
|
||||
}
|
||||
midiMap.put(note, armedMidi);
|
||||
armedMidi.setFill(Color.color(0, 1, 0));
|
||||
armedMidiPaint = null;
|
||||
armedMidi = null;
|
||||
}
|
||||
if (midiMap.containsKey(note)) {
|
||||
midiMap.get(note).setFill(Color.color(1, 1, 1));
|
||||
Slider slider = midiButtonMap.get(midiMap.get(note));
|
||||
|
||||
slider.setValue(midiPressureToPressure(slider, midiPressure));
|
||||
}
|
||||
midiMap.put(note, armedMidi);
|
||||
armedMidi.setFill(Color.color(0, 1, 0));
|
||||
armedMidiPaint = null;
|
||||
armedMidi = null;
|
||||
}
|
||||
if (midiMap.containsKey(note)) {
|
||||
Slider slider = midiButtonMap.get(midiMap.get(note));
|
||||
double max = slider.getMax();
|
||||
double min = slider.getMin();
|
||||
double range = max - min;
|
||||
slider.setValue(min + (pressure / 127.0) * range);
|
||||
}
|
||||
}
|
||||
|
||||
// gets the volume/scale
|
||||
@Override
|
||||
public Double getValue() {
|
||||
return scaleSlider.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Double scale) {
|
||||
scaleSlider.setValue(scale);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,11 +100,25 @@
|
|||
<Label layoutX="120.0" layoutY="18.0" text="x :" />
|
||||
<Label layoutX="219.0" layoutY="18.0" text="y :" />
|
||||
<TextField fx:id="translationYTextField" layoutX="238.0" layoutY="15.0" prefHeight="26.0" prefWidth="70.0" text="0" />
|
||||
<Slider fx:id="frequencySlider" blockIncrement="0.001" layoutX="116.0" layoutY="45.0" majorTickUnit="0.1" max="1.0" prefHeight="42.0" prefWidth="253.0" showTickMarks="true" value="0.56212" />
|
||||
<Slider fx:id="frequencySlider" blockIncrement="0.001" layoutX="116.0" layoutY="45.0" majorTickUnit="0.07389" max="1.0" minorTickCount="2" prefHeight="42.0" prefWidth="253.0" showTickMarks="true" value="0.59269" />
|
||||
<SVGPath fx:id="frequencyMidi" content="M20.15 8.26H22V15.74H20.15M13 8.26H18.43C19 8.26 19.3 8.74 19.3 9.3V14.81C19.3 15.5 19 15.74 18.38 15.74H13V11H14.87V13.91H17.5V9.95H13M10.32 8.26H12.14V15.74H10.32M2 8.26H8.55C9.1 8.26 9.41 8.74 9.41 9.3V15.74H7.59V10.15H6.5V15.74H4.87V10.15H3.83V15.74H2Z" fill="WHITE" layoutX="371.0" layoutY="49.0" pickOnBounds="true" />
|
||||
<SVGPath fx:id="rotateSpeedMidi" content="M20.15 8.26H22V15.74H20.15M13 8.26H18.43C19 8.26 19.3 8.74 19.3 9.3V14.81C19.3 15.5 19 15.74 18.38 15.74H13V11H14.87V13.91H17.5V9.95H13M10.32 8.26H12.14V15.74H10.32M2 8.26H8.55C9.1 8.26 9.41 8.74 9.41 9.3V15.74H7.59V10.15H6.5V15.74H4.87V10.15H3.83V15.74H2Z" fill="WHITE" layoutX="371.0" layoutY="87.0" pickOnBounds="true" />
|
||||
<SVGPath fx:id="translationSpeedMidi" content="M20.15 8.26H22V15.74H20.15M13 8.26H18.43C19 8.26 19.3 8.74 19.3 9.3V14.81C19.3 15.5 19 15.74 18.38 15.74H13V11H14.87V13.91H17.5V9.95H13M10.32 8.26H12.14V15.74H10.32M2 8.26H8.55C9.1 8.26 9.41 8.74 9.41 9.3V15.74H7.59V10.15H6.5V15.74H4.87V10.15H3.83V15.74H2Z" fill="WHITE" layoutX="371.0" layoutY="125.0" pickOnBounds="true" />
|
||||
<SVGPath fx:id="scaleMidi" content="M20.15 8.26H22V15.74H20.15M13 8.26H18.43C19 8.26 19.3 8.74 19.3 9.3V14.81C19.3 15.5 19 15.74 18.38 15.74H13V11H14.87V13.91H17.5V9.95H13M10.32 8.26H12.14V15.74H10.32M2 8.26H8.55C9.1 8.26 9.41 8.74 9.41 9.3V15.74H7.59V10.15H6.5V15.74H4.87V10.15H3.83V15.74H2Z" fill="WHITE" layoutX="371.0" layoutY="164.0" pickOnBounds="true" />
|
||||
<Label layoutX="257.0" layoutY="71.0" text="C4" />
|
||||
<Label layoutX="239.0" layoutY="71.0" text="C3" />
|
||||
<Label layoutX="222.0" layoutY="71.0" text="C2" />
|
||||
<Label layoutX="193.0" layoutY="71.0" text="20" />
|
||||
<Label layoutX="274.0" layoutY="71.0" text="C5" />
|
||||
<Label layoutX="292.0" layoutY="71.0" text="C6" />
|
||||
<Label layoutX="310.0" layoutY="71.0" text="C7" />
|
||||
<Label layoutX="344.0" layoutY="71.0" text="12000">
|
||||
<font>
|
||||
<Font size="11.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<Label layoutX="162.0" layoutY="71.0" text="5" />
|
||||
<Label layoutX="122.0" layoutY="71.0" text="0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
|
|
Ładowanie…
Reference in New Issue