kopia lustrzana https://github.com/jameshball/osci-render
Uncomplicate and significantly improve midi performance
rodzic
0cb45b9fed
commit
37d8f39a7f
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Ładowanie…
Reference in New Issue