kopia lustrzana https://github.com/jameshball/osci-render
rodzic
18c5565d61
commit
ae7c974ec0
2
pom.xml
2
pom.xml
|
@ -6,7 +6,7 @@
|
|||
|
||||
<groupId>sh.ball</groupId>
|
||||
<artifactId>osci-render</artifactId>
|
||||
<version>1.19.0</version>
|
||||
<version>1.19.1</version>
|
||||
|
||||
<name>osci-render</name>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import sh.ball.audio.effect.Effect;
|
|||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import sh.ball.audio.effect.PhaseEffect;
|
||||
import sh.ball.audio.effect.SineEffect;
|
||||
|
@ -39,11 +40,13 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
// MIDI
|
||||
private final short[][] keyTargetVolumes = new short[MidiNote.NUM_CHANNELS][128];
|
||||
private final short[][] keyActualVolumes = new short[MidiNote.NUM_CHANNELS][128];
|
||||
private final Set<MidiNote> keysDown = ConcurrentHashMap.newKeySet();
|
||||
private final AtomicInteger numKeysDown = new AtomicInteger(1);
|
||||
private final MidiNote[][] keysDown = new MidiNote[MidiNote.NUM_CHANNELS][128];
|
||||
private final SineEffect[][] sineEffects = new SineEffect[MidiNote.NUM_CHANNELS][128];
|
||||
private boolean midiStarted = false;
|
||||
private int mainChannel = 0;
|
||||
private MidiNote baseNote = new MidiNote(60, mainChannel);
|
||||
private double[] pitchBends = new double[MidiNote.NUM_CHANNELS];
|
||||
private final double[] pitchBends = new double[MidiNote.NUM_CHANNELS];
|
||||
private int lastDecay = 0;
|
||||
private double decaySeconds = 0.2;
|
||||
private int decayFrames;
|
||||
|
@ -54,7 +57,6 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
private final Callable<AudioEngine> audioEngineBuilder;
|
||||
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
|
||||
private final Map<Object, Effect> effects = new ConcurrentHashMap<>();
|
||||
private final Map<MidiNote, SineEffect> sineEffects = new ConcurrentHashMap<>();
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
private AudioEngine audioEngine;
|
||||
|
@ -91,27 +93,28 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
}
|
||||
|
||||
public boolean midiPlaying() {
|
||||
return keysDown.size() > 0;
|
||||
return numKeysDown.get() > 0;
|
||||
}
|
||||
|
||||
public void resetMidi() {
|
||||
keysDown.clear();
|
||||
for (int i = 0; i < keyTargetVolumes.length; i++) {
|
||||
for (int i = 0; i < MidiNote.NUM_CHANNELS; i++) {
|
||||
Arrays.fill(keysDown[i], null);
|
||||
Arrays.fill(keyTargetVolumes[i], (short) 0);
|
||||
Arrays.fill(keyActualVolumes[i], (short) 0);
|
||||
}
|
||||
// Middle C is down by default
|
||||
keyTargetVolumes[0][60] = (short) MidiNote.MAX_VELOCITY;
|
||||
keyActualVolumes[0][60] = (short) MidiNote.MAX_VELOCITY;
|
||||
keysDown.add(new MidiNote(60));
|
||||
MidiNote note = new MidiNote(60);
|
||||
keysDown[note.channel()][note.key()] = note;
|
||||
midiStarted = false;
|
||||
notesChanged();
|
||||
}
|
||||
|
||||
public void stopMidiNotes() {
|
||||
keysDown.clear();
|
||||
for (short[] keyTargetVolume : keyTargetVolumes) {
|
||||
Arrays.fill(keyTargetVolume, (short) 0);
|
||||
for (int i = 0; i < MidiNote.NUM_CHANNELS; i++) {
|
||||
Arrays.fill(keysDown[i], null);
|
||||
Arrays.fill(keyTargetVolumes[i], (short) 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -245,9 +248,11 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
lastAttack = 0;
|
||||
}
|
||||
lastAttack++;
|
||||
for (MidiNote note : keysDown) {
|
||||
if (!note.equals(baseNote)) {
|
||||
vector = sineEffects.get(note).apply(frame, vector);
|
||||
for (int channel = 0; channel < keysDown.length; channel++) {
|
||||
for (int key = 0; key < keysDown[0].length; key++) {
|
||||
if (keysDown[channel][key] != null && !keysDown[channel][key].equals(baseNote)) {
|
||||
vector = sineEffects[channel][key].apply(frame, vector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -405,12 +410,14 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
@Override
|
||||
public void setDevice(AudioDevice device) {
|
||||
this.device = device;
|
||||
sineEffects.clear();
|
||||
for (int i = 0; i < MidiNote.NUM_CHANNELS; i++) {
|
||||
Arrays.fill(sineEffects[i], null);
|
||||
}
|
||||
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.put(note, new SineEffect(sampleRate, note.frequency(), keyActualVolumes[channel][key] / MidiNote.MAX_VELOCITY));
|
||||
sineEffects[channel][key] = new SineEffect(sampleRate, note.frequency(), keyActualVolumes[channel][key] / MidiNote.MAX_VELOCITY);
|
||||
}
|
||||
}
|
||||
for (Effect effect : effects.values()) {
|
||||
|
@ -471,10 +478,12 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
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 && sineEffects.size() != 0) {
|
||||
SineEffect effect = sineEffects.get(note);
|
||||
effect.setVolume(scaledVolume * keyActualVolumes[channel][key] / MidiNote.MAX_VELOCITY);
|
||||
effect.setFrequency(note.frequency() * pitchBends[channel]);
|
||||
if (keyActualVolumes[channel][key] > 0) {
|
||||
SineEffect effect = sineEffects[channel][key];
|
||||
if (effect != null) {
|
||||
effect.setVolume(scaledVolume * keyActualVolumes[channel][key] / MidiNote.MAX_VELOCITY);
|
||||
effect.setFrequency(note.frequency() * pitchBends[channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -520,10 +529,12 @@ public class ShapeAudioPlayer implements AudioPlayer<List<Shape>> {
|
|||
|
||||
if (command == ShortMessage.NOTE_OFF || velocity == 0) {
|
||||
keyTargetVolumes[note.channel()][note.key()] = 0;
|
||||
keysDown.remove(note);
|
||||
keysDown[note.channel()][note.key()] = null;
|
||||
numKeysDown.getAndDecrement();
|
||||
} else {
|
||||
keyTargetVolumes[note.channel()][note.key()] = (short) velocity;
|
||||
keysDown.add(note);
|
||||
keysDown[note.channel()][note.key()] = note;
|
||||
numKeysDown.getAndIncrement();
|
||||
}
|
||||
notesChanged();
|
||||
} else if (command == ShortMessage.PITCH_BEND) {
|
||||
|
|
|
@ -9,6 +9,7 @@ public class EffectAnimator extends PhaseEffect implements SettableEffect {
|
|||
private final SettableEffect effect;
|
||||
|
||||
private AnimationType type = AnimationType.STATIC;
|
||||
private boolean justSetToStatic = true;
|
||||
private double targetValue = 0.5;
|
||||
private double actualValue = 0.5;
|
||||
private boolean linearDirection = true;
|
||||
|
@ -29,6 +30,9 @@ public class EffectAnimator extends PhaseEffect implements SettableEffect {
|
|||
public void setAnimation(AnimationType type) {
|
||||
this.type = type;
|
||||
this.linearDirection = true;
|
||||
if (type == AnimationType.STATIC) {
|
||||
justSetToStatic = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMin(double min) {
|
||||
|
@ -50,7 +54,14 @@ public class EffectAnimator extends PhaseEffect implements SettableEffect {
|
|||
double normalisedTargetValue = (targetValue - minValue) / range;
|
||||
double normalisedActualValue = (actualValue - minValue) / range;
|
||||
switch (type) {
|
||||
case STATIC -> actualValue = targetValue;
|
||||
case STATIC -> {
|
||||
if (justSetToStatic) {
|
||||
actualValue = targetValue;
|
||||
justSetToStatic = false;
|
||||
effect.setValue(actualValue);
|
||||
}
|
||||
return effect.apply(count, vector);
|
||||
}
|
||||
case SEESAW -> {
|
||||
double scalar = 10 * Math.max(Math.min(normalisedActualValue, 1 - normalisedActualValue), 0.01);
|
||||
double change = range * scalar * SPEED_SCALE * normalisedTargetValue / sampleRate;
|
||||
|
|
|
@ -7,56 +7,47 @@ import java.util.List;
|
|||
|
||||
public class SmoothEffect implements SettableEffect {
|
||||
|
||||
private List<Vector2> window;
|
||||
private static final int MAX_WINDOW_SIZE = 2048;
|
||||
|
||||
private final Vector2[] window;
|
||||
private int windowSize;
|
||||
private int head = 0;
|
||||
|
||||
public SmoothEffect(int windowSize) {
|
||||
this.windowSize = windowSize <= 0 ? 1 : windowSize;
|
||||
this.window = new ArrayList<>();
|
||||
for (int i = 0; i < windowSize; i++) {
|
||||
window.add(null);
|
||||
}
|
||||
this.window = new Vector2[MAX_WINDOW_SIZE];
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setValue(double value) {
|
||||
int windowSize = (int) (256 * value);
|
||||
int oldWindowSize = this.windowSize;
|
||||
this.windowSize = windowSize <= 0 ? 1 : windowSize;
|
||||
List<Vector2> newWindow = new ArrayList<>();
|
||||
for (int i = 0; i < this.windowSize; i++) {
|
||||
newWindow.add(null);
|
||||
}
|
||||
for (int i = 0; i < Math.min(this.windowSize, oldWindowSize); i++) {
|
||||
newWindow.set(i, window.get(head++));
|
||||
if (head >= window.size()) {
|
||||
head = 0;
|
||||
}
|
||||
}
|
||||
head = 0;
|
||||
window = newWindow;
|
||||
this.windowSize = Math.max(1, Math.min(MAX_WINDOW_SIZE, windowSize));
|
||||
}
|
||||
|
||||
// could be made much more efficient by just subbing prev vector and adding
|
||||
// new vector to the aggregate previous average
|
||||
@Override
|
||||
public synchronized Vector2 apply(int count, Vector2 vector) {
|
||||
window.set(head, vector);
|
||||
head++;
|
||||
if (head >= windowSize) {
|
||||
window[head++] = vector;
|
||||
if (head >= MAX_WINDOW_SIZE) {
|
||||
head = 0;
|
||||
}
|
||||
double totalX = 0;
|
||||
double totalY = 0;
|
||||
int size = 0;
|
||||
|
||||
for (Vector2 v : window) {
|
||||
if (v != null) {
|
||||
totalX += v.getX();
|
||||
totalY += v.getY();
|
||||
size++;
|
||||
int newHead = head - 1;
|
||||
for (int i = 0; i < windowSize; i++) {
|
||||
if (newHead < 0) {
|
||||
newHead = MAX_WINDOW_SIZE - 1;
|
||||
}
|
||||
|
||||
if (window[newHead] != null) {
|
||||
totalX += window[newHead].getX();
|
||||
totalY += window[newHead].getY();
|
||||
}
|
||||
|
||||
newHead--;
|
||||
}
|
||||
|
||||
return new Vector2(totalX / size, totalY / size);
|
||||
return new Vector2(totalX / windowSize, totalY / windowSize);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class MidiNote {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, channel);
|
||||
return (key << 16) + channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -60,8 +60,9 @@ public class ObjController implements Initializable, SubController {
|
|||
|
||||
// changes the rotateSpeed of the FrameProducer
|
||||
public void setObjectRotateSpeed(double rotateSpeed) {
|
||||
double actualSpeed = (Math.exp(3 * Math.min(10, Math.abs(rotateSpeed))) - 1) / 50;
|
||||
producer.setFrameSettings(
|
||||
ObjSettingsFactory.rotateSpeed((Math.exp(3 * rotateSpeed) - 1) / 50)
|
||||
ObjSettingsFactory.rotateSpeed(rotateSpeed > 0 ? actualSpeed : -actualSpeed)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue