kopia lustrzana https://github.com/jameshball/osci-render
WIP: Add channels to MIDI playback
rodzic
0afd92009c
commit
df9ec401e4
|
@ -22,6 +22,8 @@ import javax.sound.midi.ShortMessage;
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
import javax.sound.sampled.AudioInputStream;
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
|
||||||
|
import static sh.ball.gui.Gui.audioPlayer;
|
||||||
|
|
||||||
public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
|
|
||||||
private static final double MIN_TRACE = 0.001;
|
private static final double MIN_TRACE = 0.001;
|
||||||
|
@ -35,13 +37,13 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
// Stereo audio
|
// Stereo audio
|
||||||
private static final int NUM_OUTPUTS = 2;
|
private static final int NUM_OUTPUTS = 2;
|
||||||
private static final double MIN_LENGTH_INCREMENT = 0.0000000001;
|
private static final double MIN_LENGTH_INCREMENT = 0.0000000001;
|
||||||
public static final double EPSILON = 0.001;
|
|
||||||
|
|
||||||
// MIDI
|
// MIDI
|
||||||
private final short[] keyTargetVolumes = new short[128];
|
private final short[][] keyTargetVolumes = new short[16][128];
|
||||||
private final short[] keyActualVolumes = new short[128];
|
private final short[][] keyActualVolumes = new short[16][128];
|
||||||
private final Set<Integer> keysDown = ConcurrentHashMap.newKeySet();
|
private final Set<MidiNote> keysDown = ConcurrentHashMap.newKeySet();
|
||||||
private boolean midiStarted = false;
|
private boolean midiStarted = false;
|
||||||
|
private int mainChannel = 0;
|
||||||
private int baseKey = 60;
|
private int baseKey = 60;
|
||||||
private double pitchBend = 1.0;
|
private double pitchBend = 1.0;
|
||||||
private int lastDecay = 0;
|
private int lastDecay = 0;
|
||||||
|
@ -54,7 +56,7 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
private final Callable<AudioEngine> audioEngineBuilder;
|
private final Callable<AudioEngine> audioEngineBuilder;
|
||||||
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
|
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
|
||||||
private final Map<Object, Effect> effects = new ConcurrentHashMap<>();
|
private final Map<Object, Effect> effects = new ConcurrentHashMap<>();
|
||||||
private final List<SineEffect> sineEffects = new CopyOnWriteArrayList<>();
|
private final Map<MidiNote, SineEffect> sineEffects = new ConcurrentHashMap<>();
|
||||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
private AudioEngine audioEngine;
|
private AudioEngine audioEngine;
|
||||||
|
@ -89,13 +91,25 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetMidi() {
|
private void resetMidi() {
|
||||||
|
keysDown.clear();
|
||||||
|
for (int i = 0; i < keyTargetVolumes.length; i++) {
|
||||||
|
Arrays.fill(keyTargetVolumes[i], (short) 0);
|
||||||
|
Arrays.fill(keyActualVolumes[i], (short) 0);
|
||||||
|
}
|
||||||
// Middle C is down by default
|
// Middle C is down by default
|
||||||
keyTargetVolumes[60] = (short) MidiNote.MAX_VELOCITY;
|
keyTargetVolumes[0][60] = (short) MidiNote.MAX_VELOCITY;
|
||||||
keyActualVolumes[60] = (short) MidiNote.MAX_VELOCITY;
|
keyActualVolumes[0][60] = (short) MidiNote.MAX_VELOCITY;
|
||||||
keysDown.add(60);
|
keysDown.add(new MidiNote(60));
|
||||||
midiStarted = false;
|
midiStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stopMidiNotes() {
|
||||||
|
keysDown.clear();
|
||||||
|
for (short[] keyTargetVolume : keyTargetVolumes) {
|
||||||
|
Arrays.fill(keyTargetVolume, (short) 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setFrequency(DoubleProperty frequency) {
|
public void setFrequency(DoubleProperty frequency) {
|
||||||
this.frequency = frequency;
|
this.frequency = frequency;
|
||||||
frequency.addListener((o, old, f) -> setBaseFrequency(f.doubleValue()));
|
frequency.addListener((o, old, f) -> setBaseFrequency(f.doubleValue()));
|
||||||
|
@ -192,12 +206,14 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 applyEffects(int frame, Vector2 vector) {
|
private Vector2 applyEffects(int frame, Vector2 vector) {
|
||||||
vector = vector.scale(2 * baseFrequencyVolumeScale * keyActualVolumes[baseKey] / MidiNote.MAX_VELOCITY);
|
vector = vector.scale(2 * baseFrequencyVolumeScale * keyActualVolumes[mainChannel][baseKey] / MidiNote.MAX_VELOCITY);
|
||||||
if (midiStarted) {
|
if (midiStarted) {
|
||||||
if (lastDecay > decayFrames) {
|
if (lastDecay > decayFrames) {
|
||||||
for (int i = 0; i < keyActualVolumes.length; i++) {
|
for (int i = 0; i < keyActualVolumes.length; i++) {
|
||||||
if (keyActualVolumes[i] > keyTargetVolumes[i]) {
|
for (int j = 0; j < keyActualVolumes[i].length; j++) {
|
||||||
keyActualVolumes[i]--;
|
if (keyActualVolumes[i][j] > keyTargetVolumes[i][j]) {
|
||||||
|
keyActualVolumes[i][j]--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastDecay = 0;
|
lastDecay = 0;
|
||||||
|
@ -205,17 +221,18 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
lastDecay++;
|
lastDecay++;
|
||||||
if (lastAttack > attackFrames) {
|
if (lastAttack > attackFrames) {
|
||||||
for (int i = 0; i < keyActualVolumes.length; i++) {
|
for (int i = 0; i < keyActualVolumes.length; i++) {
|
||||||
if (keyActualVolumes[i] < keyTargetVolumes[i]) {
|
for (int j = 0; j < keyActualVolumes[i].length; j++) {
|
||||||
keyActualVolumes[i]++;
|
if (keyActualVolumes[i][j] < keyTargetVolumes[i][j]) {
|
||||||
|
keyActualVolumes[i][j]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastAttack = 0;
|
lastAttack = 0;
|
||||||
}
|
}
|
||||||
lastAttack++;
|
lastAttack++;
|
||||||
for (int key : keysDown) {
|
for (MidiNote note : keysDown) {
|
||||||
double frequency = new MidiNote(key).frequency();
|
if (note.channel() != mainChannel) {
|
||||||
if (Math.abs(frequency - baseFrequency) > EPSILON) {
|
vector = sineEffects.get(note).apply(frame, vector);
|
||||||
vector = sineEffects.get(key).apply(frame, vector);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,6 +348,11 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
updateSineEffects();
|
updateSineEffects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMainMidiChannel(int channel) {
|
||||||
|
this.mainChannel = channel;
|
||||||
|
notesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFrame(List<Shape> frame) {
|
public void addFrame(List<Shape> frame) {
|
||||||
try {
|
try {
|
||||||
|
@ -371,8 +393,11 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
sineEffects.clear();
|
sineEffects.clear();
|
||||||
this.sampleRate = device.sampleRate();
|
this.sampleRate = device.sampleRate();
|
||||||
for (int i = 0; i < keyActualVolumes.length; i++) {
|
for (int channel = 0; channel < keyActualVolumes.length; channel++) {
|
||||||
sineEffects.add(new SineEffect(sampleRate, new MidiNote(i).frequency(), keyActualVolumes[i] / MidiNote.MAX_VELOCITY));
|
for (int key = 0; key < keyActualVolumes[channel].length; key++) {
|
||||||
|
MidiNote note = new MidiNote(key, channel);
|
||||||
|
sineEffects.put(note, new SineEffect(sampleRate, note.frequency(), keyActualVolumes[channel][key] / MidiNote.MAX_VELOCITY));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (Effect effect : effects.values()) {
|
for (Effect effect : effects.values()) {
|
||||||
if (effect instanceof PhaseEffect phase) {
|
if (effect instanceof PhaseEffect phase) {
|
||||||
|
@ -422,35 +447,37 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
|
|
||||||
private void updateSineEffects() {
|
private void updateSineEffects() {
|
||||||
double totalVolume = 0;
|
double totalVolume = 0;
|
||||||
for (short volume : keyActualVolumes) {
|
for (short[] volumes : keyActualVolumes) {
|
||||||
|
for (short volume : volumes) {
|
||||||
totalVolume += volume / MidiNote.MAX_VELOCITY;
|
totalVolume += volume / MidiNote.MAX_VELOCITY;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (totalVolume != 0) {
|
if (totalVolume != 0) {
|
||||||
double scaledVolume = (1 - baseFrequencyVolumeScale) / totalVolume;
|
double scaledVolume = (1 - baseFrequencyVolumeScale) / totalVolume;
|
||||||
for (int i = 0; i < keyActualVolumes.length; i++) {
|
for (int channel = 0; channel < keyActualVolumes.length; channel++) {
|
||||||
double frequency = new MidiNote(i).frequency();
|
for (int key = 0; key < keyActualVolumes[channel].length; key++) {
|
||||||
if (keyActualVolumes[i] > 0 && sineEffects.size() != 0) {
|
MidiNote note = new MidiNote(key, channel);
|
||||||
SineEffect effect = sineEffects.get(i);
|
if (keyActualVolumes[channel][key] > 0 && sineEffects.size() != 0) {
|
||||||
effect.setVolume(scaledVolume * keyActualVolumes[i] / MidiNote.MAX_VELOCITY);
|
SineEffect effect = sineEffects.get(note);
|
||||||
effect.setFrequency(frequency * pitchBend);
|
effect.setVolume(scaledVolume * keyActualVolumes[channel][key] / MidiNote.MAX_VELOCITY);
|
||||||
|
effect.setFrequency(note.frequency() * pitchBend);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notesChanged() {
|
private void notesChanged() {
|
||||||
int loudestKey = 0;
|
MidiNote note = null;
|
||||||
int maxVelocity = 0;
|
for (int key = 0; key < keyTargetVolumes[mainChannel].length; key++) {
|
||||||
for (int i = 0; i < keyTargetVolumes.length; i++) {
|
note = new MidiNote(key, mainChannel);
|
||||||
if (keyTargetVolumes[i] > maxVelocity && keysDown.contains(i)) {
|
if (keysDown.contains(note)) {
|
||||||
loudestKey = i;
|
break;
|
||||||
maxVelocity = keyTargetVolumes[i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (maxVelocity > 0) {
|
if (note != null) {
|
||||||
double baseFrequency = new MidiNote(loudestKey).frequency();
|
frequency.set(note.frequency());
|
||||||
frequency.set(baseFrequency);
|
baseKey = note.key();
|
||||||
baseKey = loudestKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSineEffects();
|
updateSineEffects();
|
||||||
|
@ -472,17 +499,30 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
||||||
keysDown.clear();
|
keysDown.clear();
|
||||||
midiStarted = true;
|
midiStarted = true;
|
||||||
}
|
}
|
||||||
MidiNote note = new MidiNote(message.getData1());
|
MidiNote note = new MidiNote(message.getData1(), message.getChannel());
|
||||||
int velocity = message.getData2();
|
int velocity = message.getData2();
|
||||||
|
|
||||||
if (command == ShortMessage.NOTE_OFF) {
|
if (command == ShortMessage.NOTE_OFF) {
|
||||||
keyTargetVolumes[note.key()] = 0;
|
keyTargetVolumes[note.channel()][note.key()] = 0;
|
||||||
keysDown.remove(note.key());
|
keysDown.remove(note);
|
||||||
} else {
|
} else {
|
||||||
keyTargetVolumes[note.key()] = (short) velocity;
|
keyTargetVolumes[note.channel()][note.key()] = (short) velocity;
|
||||||
keysDown.add(note.key());
|
keysDown.add(note);
|
||||||
}
|
}
|
||||||
notesChanged();
|
notesChanged();
|
||||||
|
} else if (command == ShortMessage.PITCH_BEND) {
|
||||||
|
// using these instructions https://sites.uci.edu/camp2014/2014/04/30/managing-midi-pitchbend-messages/
|
||||||
|
|
||||||
|
int pitchBend = (message.getData2() << MidiNote.PITCH_BEND_DATA_LENGTH) | message.getData1();
|
||||||
|
// get pitch bend in range -1 to 1
|
||||||
|
double pitchBendFactor = (double) pitchBend / MidiNote.PITCH_BEND_MAX;
|
||||||
|
pitchBendFactor = 2 * pitchBendFactor - 1;
|
||||||
|
pitchBendFactor *= MidiNote.PITCH_BEND_SEMITONES;
|
||||||
|
// 12 tone equal temperament
|
||||||
|
pitchBendFactor /= 12;
|
||||||
|
pitchBendFactor = Math.pow(2, pitchBendFactor);
|
||||||
|
|
||||||
|
audioPlayer.setPitchBendFactor(pitchBendFactor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ import java.util.Objects;
|
||||||
public class MidiNote {
|
public class MidiNote {
|
||||||
|
|
||||||
public static double MAX_VELOCITY = 127;
|
public static double MAX_VELOCITY = 127;
|
||||||
|
public static short MAX_CC = 0x78;
|
||||||
|
public static short MAX_CHANNEL = 15;
|
||||||
|
public static short ALL_NOTES_OFF = 0x7B;
|
||||||
public static final double MIDDLE_C = 261.6255798;
|
public static final double MIDDLE_C = 261.6255798;
|
||||||
public static final int PITCH_BEND_DATA_LENGTH = 7;
|
public static final int PITCH_BEND_DATA_LENGTH = 7;
|
||||||
public static final int PITCH_BEND_MAX = 16383;
|
public static final int PITCH_BEND_MAX = 16383;
|
||||||
|
@ -19,14 +22,20 @@ public class MidiNote {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final int key;
|
private final int key;
|
||||||
private final int octave;
|
private final int octave;
|
||||||
|
private final int channel;
|
||||||
|
|
||||||
public MidiNote(int key) {
|
public MidiNote(int key, int channel) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
this.channel = channel;
|
||||||
this.octave = (key / 12)-1;
|
this.octave = (key / 12)-1;
|
||||||
int note = key % 12;
|
int note = key % 12;
|
||||||
this.name = NOTE_NAMES[note];
|
this.name = NOTE_NAMES[note];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MidiNote(int key) {
|
||||||
|
this(key, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public int key() {
|
public int key() {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
@ -36,17 +45,21 @@ public class MidiNote {
|
||||||
return (float) (A4 * Math.pow(2, (key - KEY_A4) / 12d));
|
return (float) (A4 * Math.pow(2, (key - KEY_A4) / 12d));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int channel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
MidiNote midiNote = (MidiNote) o;
|
MidiNote midiNote = (MidiNote) o;
|
||||||
return key == midiNote.key;
|
return key == midiNote.key && channel == midiNote.channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(key);
|
return Objects.hash(key, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,6 +5,7 @@ import javafx.scene.control.*;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.paint.Paint;
|
import javafx.scene.paint.Paint;
|
||||||
import javafx.scene.shape.SVGPath;
|
import javafx.scene.shape.SVGPath;
|
||||||
|
import javafx.util.converter.IntegerStringConverter;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
@ -12,8 +13,11 @@ import sh.ball.audio.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.text.ParsePosition;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
@ -88,6 +92,12 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
private MenuItem saveProjectMenuItem;
|
private MenuItem saveProjectMenuItem;
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem saveAsProjectMenuItem;
|
private MenuItem saveAsProjectMenuItem;
|
||||||
|
@FXML
|
||||||
|
private MenuItem resetMidiMappingMenuItem;
|
||||||
|
@FXML
|
||||||
|
private MenuItem stopMidiNotesMenuItem;
|
||||||
|
@FXML
|
||||||
|
private Spinner<Integer> midiChannelSpinner;
|
||||||
|
|
||||||
public MainController() throws Exception {
|
public MainController() throws Exception {
|
||||||
// Clone DEFAULT_OBJ InputStream using a ByteArrayOutputStream
|
// Clone DEFAULT_OBJ InputStream using a ByteArrayOutputStream
|
||||||
|
@ -107,9 +117,9 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
}
|
}
|
||||||
this.sampleRate = defaultDevice.sampleRate();
|
this.sampleRate = defaultDevice.sampleRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialises midiButtonMap by mapping MIDI logo SVGs to the slider that they
|
// initialises midiButtonMap by mapping MIDI logo SVGs to the slider that they
|
||||||
// control if they are selected.
|
// control if they are selected.
|
||||||
|
|
||||||
private Map<SVGPath, Slider> initializeMidiButtonMap() {
|
private Map<SVGPath, Slider> initializeMidiButtonMap() {
|
||||||
Map<SVGPath, Slider> midiMap = new HashMap<>();
|
Map<SVGPath, Slider> midiMap = new HashMap<>();
|
||||||
subControllers().forEach(controller -> midiMap.putAll(controller.getMidiButtonMap()));
|
subControllers().forEach(controller -> midiMap.putAll(controller.getMidiButtonMap()));
|
||||||
|
@ -177,6 +187,29 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
resetMidiMappingMenuItem.setOnAction(e -> resetCCMap());
|
||||||
|
|
||||||
|
stopMidiNotesMenuItem.setOnAction(e -> audioPlayer.stopMidiNotes());
|
||||||
|
|
||||||
|
NumberFormat format = NumberFormat.getIntegerInstance();
|
||||||
|
UnaryOperator<TextFormatter.Change> filter = c -> {
|
||||||
|
if (c.isContentChange()) {
|
||||||
|
ParsePosition parsePosition = new ParsePosition(0);
|
||||||
|
// NumberFormat evaluates the beginning of the text
|
||||||
|
format.parse(c.getControlNewText(), parsePosition);
|
||||||
|
if (parsePosition.getIndex() == 0 ||
|
||||||
|
parsePosition.getIndex() < c.getControlNewText().length()) {
|
||||||
|
// reject parsing the complete text failed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
|
midiChannelSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, MidiNote.MAX_CHANNEL));
|
||||||
|
midiChannelSpinner.getEditor().setTextFormatter(new TextFormatter<>(new IntegerStringConverter(), 0, filter));
|
||||||
|
midiChannelSpinner.valueProperty().addListener((o, oldValue, newValue) -> audioPlayer.setMainMidiChannel(newValue));
|
||||||
|
|
||||||
objController.updateObjectRotateSpeed();
|
objController.updateObjectRotateSpeed();
|
||||||
|
|
||||||
switchAudioDevice(defaultDevice, false);
|
switchAudioDevice(defaultDevice, false);
|
||||||
|
@ -388,7 +421,7 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
}
|
}
|
||||||
// If there is a slider associated with the MIDI channel, update the value
|
// If there is a slider associated with the MIDI channel, update the value
|
||||||
// of it
|
// of it
|
||||||
if (CCMap.containsKey(cc)) {
|
if (cc <= MidiNote.MAX_CC && CCMap.containsKey(cc)) {
|
||||||
Slider slider = midiButtonMap.get(CCMap.get(cc));
|
Slider slider = midiButtonMap.get(CCMap.get(cc));
|
||||||
double sliderValue = midiPressureToPressure(slider, value);
|
double sliderValue = midiPressureToPressure(slider, value);
|
||||||
|
|
||||||
|
@ -397,23 +430,12 @@ public class MainController implements Initializable, FrequencyListener, MidiLis
|
||||||
sliderValue = increment * (Math.round(sliderValue / increment));
|
sliderValue = increment * (Math.round(sliderValue / increment));
|
||||||
}
|
}
|
||||||
slider.setValue(sliderValue);
|
slider.setValue(sliderValue);
|
||||||
|
} else if (cc == MidiNote.ALL_NOTES_OFF) {
|
||||||
|
audioPlayer.stopMidiNotes();
|
||||||
}
|
}
|
||||||
} else if (command == ShortMessage.PROGRAM_CHANGE) {
|
} else if (command == ShortMessage.PROGRAM_CHANGE) {
|
||||||
// We want to change the file that is currently playing
|
// We want to change the file that is currently playing
|
||||||
Platform.runLater(() -> changeFrameSource(message.getMessage()[1]));
|
Platform.runLater(() -> changeFrameSource(message.getMessage()[1]));
|
||||||
} else if (command == ShortMessage.PITCH_BEND) {
|
|
||||||
// using these instructions https://sites.uci.edu/camp2014/2014/04/30/managing-midi-pitchbend-messages/
|
|
||||||
|
|
||||||
int pitchBend = (message.getData2() << MidiNote.PITCH_BEND_DATA_LENGTH) | message.getData1();
|
|
||||||
// get pitch bend in range -1 to 1
|
|
||||||
double pitchBendFactor = (double) pitchBend / MidiNote.PITCH_BEND_MAX;
|
|
||||||
pitchBendFactor = 2 * pitchBendFactor - 1;
|
|
||||||
pitchBendFactor *= MidiNote.PITCH_BEND_SEMITONES;
|
|
||||||
// 12 tone equal temperament
|
|
||||||
pitchBendFactor /= 12;
|
|
||||||
pitchBendFactor = Math.pow(2, pitchBendFactor);
|
|
||||||
|
|
||||||
audioPlayer.setPitchBendFactor(pitchBendFactor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.CustomMenuItem?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.Menu?>
|
<?import javafx.scene.control.Menu?>
|
||||||
<?import javafx.scene.control.MenuBar?>
|
<?import javafx.scene.control.MenuBar?>
|
||||||
<?import javafx.scene.control.MenuItem?>
|
<?import javafx.scene.control.MenuItem?>
|
||||||
|
<?import javafx.scene.control.Spinner?>
|
||||||
<?import javafx.scene.control.TitledPane?>
|
<?import javafx.scene.control.TitledPane?>
|
||||||
<?import javafx.scene.input.KeyCodeCombination?>
|
<?import javafx.scene.input.KeyCodeCombination?>
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
|
||||||
<AnchorPane prefHeight="658.0" prefWidth="837.0" stylesheets="@../css/main.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sh.ball.gui.controller.MainController">
|
<AnchorPane prefHeight="658.0" prefWidth="837.0" stylesheets="@../css/main.css" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sh.ball.gui.controller.MainController">
|
||||||
<TitledPane animated="false" collapsible="false" layoutX="424.0"
|
<TitledPane animated="false" collapsible="false" layoutX="424.0" layoutY="38.0" prefHeight="387.0" prefWidth="402.0" text="Audio Effects">
|
||||||
layoutY="38.0" prefHeight="387.0" prefWidth="402.0"
|
<fx:include fx:id="effects" source="effects.fxml" />
|
||||||
text="Audio Effects">
|
|
||||||
<fx:include fx:id="effects" source="effects.fxml"/>
|
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<TitledPane fx:id="objTitledPane" animated="false" collapsible="false" layoutX="424.0" layoutY="436.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="213.0" prefWidth="402.0" text="3D .obj file settings">
|
<TitledPane fx:id="objTitledPane" animated="false" collapsible="false" layoutX="424.0" layoutY="436.0" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="213.0" prefWidth="402.0" text="3D .obj file settings">
|
||||||
<fx:include fx:id="obj" source="obj.fxml"/>
|
<fx:include fx:id="obj" source="obj.fxml" />
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<fx:include fx:id="general" source="general.fxml"/>
|
<fx:include fx:id="general" source="general.fxml" />
|
||||||
<TitledPane collapsible="false" layoutX="10.0" layoutY="363.0" prefHeight="286.0" prefWidth="402.0" text="Image settings">
|
<TitledPane collapsible="false" layoutX="10.0" layoutY="363.0" prefHeight="286.0" prefWidth="402.0" text="Image settings">
|
||||||
<fx:include fx:id="image" source="image.fxml"/>
|
<fx:include fx:id="image" source="image.fxml" />
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<MenuBar prefHeight="27.0" prefWidth="838.0">
|
<MenuBar prefHeight="27.0" prefWidth="838.0">
|
||||||
<menus>
|
<menus>
|
||||||
|
@ -41,6 +42,25 @@
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
<Menu mnemonicParsing="false" text="MIDI">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="resetMidiMappingMenuItem" mnemonicParsing="false" text="Reset MIDI Mappings">
|
||||||
|
<accelerator>
|
||||||
|
<KeyCodeCombination alt="UP" code="M" control="UP" meta="UP" shift="UP" shortcut="DOWN" />
|
||||||
|
</accelerator></MenuItem>
|
||||||
|
<MenuItem fx:id="stopMidiNotesMenuItem" mnemonicParsing="false" text="Stop MIDI Notes" />
|
||||||
|
<CustomMenuItem hideOnClick="false" mnemonicParsing="false" text="MIDI Channel">
|
||||||
|
<content>
|
||||||
|
<AnchorPane>
|
||||||
|
<children>
|
||||||
|
<Label prefHeight="25.0" text="Main MIDI Channel" textFill="WHITE" />
|
||||||
|
<Spinner fx:id="midiChannelSpinner" editable="true" layoutY="25.0" />
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
|
</content>
|
||||||
|
</CustomMenuItem>
|
||||||
|
</items>
|
||||||
|
</Menu>
|
||||||
</menus>
|
</menus>
|
||||||
</MenuBar>
|
</MenuBar>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
|
|
Ładowanie…
Reference in New Issue