Uncomplicate and significantly improve midi performance

pull/137/head
James Ball 2022-11-03 23:24:29 +00:00 zatwierdzone przez James H Ball
rodzic 0cb45b9fed
commit 37d8f39a7f
2 zmienionych plików z 36 dodań i 57 usunięć

Wyświetl plik

@ -36,10 +36,10 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
private static final double MIN_LENGTH_INCREMENT = 0.000001; private static final double MIN_LENGTH_INCREMENT = 0.000001;
// MIDI // MIDI
private final short[][] keyTargetVolumes = new short[MidiNote.NUM_CHANNELS][128]; private final short[] keyTargetVolumes = new short[MidiNote.NUM_CHANNELS * MidiNote.NUM_KEYS];
private final short[][] keyActualVolumes = new short[MidiNote.NUM_CHANNELS][128]; private final short[] keyActualVolumes = new short[MidiNote.NUM_CHANNELS * MidiNote.NUM_KEYS];
private final AtomicInteger numKeysDown = new AtomicInteger(1); private final AtomicInteger numKeysDown = new AtomicInteger(1);
private final SineEffect[][] sineEffects = new SineEffect[MidiNote.NUM_CHANNELS][128]; private final int[] sinePhase = new int[MidiNote.NUM_CHANNELS * MidiNote.NUM_KEYS];
private boolean midiStarted = false; private boolean midiStarted = false;
private int mainChannel = 0; private int mainChannel = 0;
private MidiNote baseNote = new MidiNote(60, mainChannel); private MidiNote baseNote = new MidiNote(60, mainChannel);
@ -103,21 +103,17 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
} }
public void resetMidi() { public void resetMidi() {
for (int i = 0; i < MidiNote.NUM_CHANNELS; i++) { Arrays.fill(keyTargetVolumes, (short) 0);
Arrays.fill(keyTargetVolumes[i], (short) 0); Arrays.fill(keyActualVolumes, (short) 0);
Arrays.fill(keyActualVolumes[i], (short) 0);
}
// Middle C is down by default // Middle C is down by default
keyTargetVolumes[0][60] = (short) MidiNote.MAX_VELOCITY; keyTargetVolumes[60] = (short) MidiNote.MAX_VELOCITY;
keyActualVolumes[0][60] = (short) MidiNote.MAX_VELOCITY; keyActualVolumes[60] = (short) MidiNote.MAX_VELOCITY;
midiStarted = false; midiStarted = false;
notesChanged(); notesChanged();
} }
public void stopMidiNotes() { public void stopMidiNotes() {
for (int i = 0; i < MidiNote.NUM_CHANNELS; i++) { Arrays.fill(keyTargetVolumes, (short) 0);
Arrays.fill(keyTargetVolumes[i], (short) 0);
}
} }
public void setFrequency(double frequency) { public void setFrequency(double frequency) {
@ -266,14 +262,12 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
} }
private Vector2 applyEffects(int frame, Vector2 vector) { private Vector2 applyEffects(int frame, Vector2 vector) {
vector = vector.scale((double) keyActualVolumes[mainChannel][baseNote.key()] / MidiNote.MAX_VELOCITY); vector = vector.scale((double) keyActualVolumes[mainChannel * MidiNote.NUM_KEYS + baseNote.key()] / 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++) {
for (int j = 0; j < keyActualVolumes[i].length; j++) { if (keyActualVolumes[i] > keyTargetVolumes[i]) {
if (keyActualVolumes[i][j] > keyTargetVolumes[i][j]) { keyActualVolumes[i]--;
keyActualVolumes[i][j]--;
}
} }
} }
lastDecay = 0; lastDecay = 0;
@ -281,22 +275,31 @@ 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++) {
for (int j = 0; j < keyActualVolumes[i].length; j++) { if (keyActualVolumes[i] < keyTargetVolumes[i]) {
if (keyActualVolumes[i][j] < keyTargetVolumes[i][j]) { keyActualVolumes[i]++;
keyActualVolumes[i][j]++;
}
} }
} }
lastAttack = 0; lastAttack = 0;
} }
lastAttack++; lastAttack++;
for (int channel = 0; channel < keyActualVolumes.length; channel++) { for (int channel = 0; channel < MidiNote.NUM_CHANNELS; channel++) {
for (int key = 0; key < keyActualVolumes[0].length; key++) { for (int key = 0; key < MidiNote.NUM_KEYS; key++) {
if (keyActualVolumes[channel][key] > 0 && !(baseNote.key() == key && baseNote.channel() == channel)) { int index = channel * MidiNote.NUM_KEYS + key;
Vector2 sine = sineEffects[channel][key].apply(frame, new Vector2()); if (keyActualVolumes[index] > 0 && !(baseNote.key() == key && baseNote.channel() == channel)) {
double volume = backingMidiVolume * keyActualVolumes[channel][key] / MidiNote.MAX_VELOCITY; MidiNote note = new MidiNote(key, channel);
vector = new Vector2(vector.x + volume * sine.x, vector.y + volume * sine.y); int phase = sinePhase[index];
phase += 1;
double theta = 2 * Math.PI * phase / sampleRate;
double x = vector.getX() + volume * Math.sin(note.frequency() * theta);
double y = vector.getY() + volume * Math.cos(note.frequency() * theta);
sinePhase[index] = phase;
double volume = backingMidiVolume * keyActualVolumes[index] / MidiNote.MAX_VELOCITY;
vector = new Vector2(vector.x + volume * x, vector.y + volume * y);
} }
} }
} }
@ -314,13 +317,11 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
this.baseFrequency = baseFrequency; this.baseFrequency = baseFrequency;
this.octaveFrequency = baseFrequency * Math.pow(2, octave - 1); this.octaveFrequency = baseFrequency * Math.pow(2, octave - 1);
updateLengthIncrement(); updateLengthIncrement();
updateSineEffects();
} }
private void setPitchBendFactor(int channel, double pitchBend) { private void setPitchBendFactor(int channel, double pitchBend) {
pitchBends[channel] = pitchBend; pitchBends[channel] = pitchBend;
updateLengthIncrement(); updateLengthIncrement();
updateSineEffects();
} }
@Override @Override
@ -410,7 +411,6 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
@Override @Override
public void setBackingMidiVolume(double scale) { public void setBackingMidiVolume(double scale) {
this.backingMidiVolume = scale; this.backingMidiVolume = scale;
updateSineEffects();
} }
public void setMainMidiChannel(int channel) { public void setMainMidiChannel(int channel) {
@ -487,16 +487,7 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
@Override @Override
public void setDevice(AudioDevice device) { public void setDevice(AudioDevice device) {
this.device = device; this.device = device;
for (int i = 0; i < MidiNote.NUM_CHANNELS; i++) {
Arrays.fill(sineEffects[i], null);
}
this.sampleRate = device.sampleRate(); this.sampleRate = device.sampleRate();
for (int channel = 0; channel < keyActualVolumes.length; channel++) {
for (int key = 0; key < keyActualVolumes[channel].length; key++) {
MidiNote note = new MidiNote(key, channel);
sineEffects[channel][key] = new SineEffect(sampleRate, note.frequency());
}
}
for (EffectTypePair pair : effects) { for (EffectTypePair pair : effects) {
Effect effect = pair.effect(); Effect effect = pair.effect();
if (effect instanceof PhaseEffect phase) { if (effect instanceof PhaseEffect phase) {
@ -550,23 +541,9 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
audioEngine.setBrightness(brightness); audioEngine.setBrightness(brightness);
} }
private void updateSineEffects() {
for (int channel = 0; channel < keyActualVolumes.length; channel++) {
for (int key = 0; key < keyActualVolumes[channel].length; key++) {
MidiNote note = new MidiNote(key, channel);
if (keyActualVolumes[channel][key] > 0) {
SineEffect effect = sineEffects[channel][key];
if (effect != null) {
effect.setFrequency(note.frequency() * pitchBends[channel]);
}
}
}
}
}
private void notesChanged() { private void notesChanged() {
for (int key = keyTargetVolumes[mainChannel].length - 1; key >= 0; key--) { for (int key = MidiNote.NUM_KEYS - 1; key >= 0; key--) {
if (keyTargetVolumes[mainChannel][key] > 0) { if (keyTargetVolumes[mainChannel * MidiNote.NUM_KEYS + key] > 0) {
MidiNote note = new MidiNote(key, mainChannel); MidiNote note = new MidiNote(key, mainChannel);
setBaseFrequency(note.frequency()); setBaseFrequency(note.frequency());
baseNote = note; baseNote = note;
@ -599,11 +576,12 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
MidiNote note = new MidiNote(message.getData1(), message.getChannel()); MidiNote note = new MidiNote(message.getData1(), message.getChannel());
int velocity = message.getData2(); int velocity = message.getData2();
int index = note.channel() * MidiNote.NUM_KEYS + note.key();
if (command == ShortMessage.NOTE_OFF || velocity == 0) { if (command == ShortMessage.NOTE_OFF || velocity == 0) {
keyTargetVolumes[note.channel()][note.key()] = 0; keyTargetVolumes[index] = 0;
numKeysDown.getAndDecrement(); numKeysDown.getAndDecrement();
} else { } else {
keyTargetVolumes[note.channel()][note.key()] = (short) velocity; keyTargetVolumes[index] = (short) velocity;
numKeysDown.getAndIncrement(); numKeysDown.getAndIncrement();
} }
notesChanged(); notesChanged();

Wyświetl plik

@ -8,6 +8,7 @@ public class MidiNote {
public static final short MAX_CC = 0x78; public static final short MAX_CC = 0x78;
public static final short MAX_CHANNEL = 15; public static final short MAX_CHANNEL = 15;
public static final short NUM_CHANNELS = (short) (MAX_CHANNEL + 1); public static final short NUM_CHANNELS = (short) (MAX_CHANNEL + 1);
public static final short NUM_KEYS = 128;
public static final short ALL_NOTES_OFF = 0x7B; public static final 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;