kopia lustrzana https://github.com/jameshball/osci-render
Rename Renderer to AudioPlayer
rodzic
45289200a4
commit
77ede06cf4
|
@ -2,244 +2,23 @@ package sh.ball.audio;
|
||||||
|
|
||||||
import sh.ball.audio.effect.Effect;
|
import sh.ball.audio.effect.Effect;
|
||||||
|
|
||||||
import java.io.*;
|
public interface AudioPlayer<S, T> extends Runnable {
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
|
|
||||||
import sh.ball.audio.engine.AudioEngine;
|
void stop();
|
||||||
import sh.ball.shapes.Shape;
|
|
||||||
import sh.ball.shapes.Vector2;
|
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
void setQuality(double quality);
|
||||||
import javax.sound.sampled.AudioInputStream;
|
|
||||||
import java.util.concurrent.Semaphore;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
public class AudioPlayer implements Renderer<List<Shape>, AudioInputStream> {
|
void addFrame(S frame);
|
||||||
|
|
||||||
// Arbitrary max count for effects
|
void addEffect(Object identifier, Effect effect);
|
||||||
private static final int MAX_COUNT = 10000;
|
|
||||||
private static final int BUFFER_SIZE = 5;
|
|
||||||
// Is this always true? Might need to check from AudioEngine
|
|
||||||
private static final int BITS_PER_SAMPLE = 16;
|
|
||||||
private static final boolean SIGNED = true;
|
|
||||||
private static final boolean BIG_ENDIAN = false;
|
|
||||||
// Stereo audio
|
|
||||||
private static final int NUM_OUTPUTS = 2;
|
|
||||||
|
|
||||||
private final AudioEngine audioEngine;
|
void removeEffect(Object identifier);
|
||||||
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
|
|
||||||
private final Map<Object, Effect> effects = new HashMap<>();
|
|
||||||
private final ReentrantLock renderLock = new ReentrantLock();
|
|
||||||
private final List<Listener> listeners = new ArrayList<>();
|
|
||||||
|
|
||||||
private ByteArrayOutputStream outputStream;
|
void read(byte[] buffer) throws InterruptedException;
|
||||||
private boolean recording = false;
|
|
||||||
private int framesRecorded = 0;
|
|
||||||
private List<Shape> frame;
|
|
||||||
private int currentShape = 0;
|
|
||||||
private int audioFramesDrawn = 0;
|
|
||||||
private int count = 0;
|
|
||||||
|
|
||||||
private double weight = Shape.DEFAULT_WEIGHT;
|
void startRecord();
|
||||||
|
|
||||||
public AudioPlayer(AudioEngine audioEngine) {
|
int samplesPerSecond();
|
||||||
this.audioEngine = audioEngine;
|
|
||||||
}
|
T stopRecord();
|
||||||
|
|
||||||
private Vector2 generateChannels() throws InterruptedException {
|
|
||||||
Shape shape = getCurrentShape().setWeight(weight);
|
|
||||||
|
|
||||||
double totalAudioFrames = shape.getWeight() * shape.getLength();
|
|
||||||
double drawingProgress = totalAudioFrames == 0 ? 1 : audioFramesDrawn / totalAudioFrames;
|
|
||||||
Vector2 nextVector = applyEffects(count, shape.nextVector(drawingProgress));
|
|
||||||
|
|
||||||
Vector2 channels = cutoff(nextVector);
|
|
||||||
writeChannels((float) channels.getX(), (float) channels.getY());
|
|
||||||
|
|
||||||
audioFramesDrawn++;
|
|
||||||
|
|
||||||
if (++count > MAX_COUNT) {
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audioFramesDrawn > totalAudioFrames) {
|
|
||||||
audioFramesDrawn = 0;
|
|
||||||
currentShape++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentShape >= frame.size()) {
|
|
||||||
currentShape = 0;
|
|
||||||
frame = frameQueue.take();
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeChannels(float leftChannel, float rightChannel) {
|
|
||||||
int left = (int)(leftChannel * Short.MAX_VALUE);
|
|
||||||
int right = (int)(rightChannel * Short.MAX_VALUE);
|
|
||||||
|
|
||||||
byte b0 = (byte) left;
|
|
||||||
byte b1 = (byte)(left >> 8);
|
|
||||||
byte b2 = (byte) right;
|
|
||||||
byte b3 = (byte)(right >> 8);
|
|
||||||
|
|
||||||
if (recording) {
|
|
||||||
outputStream.write(b0);
|
|
||||||
outputStream.write(b1);
|
|
||||||
outputStream.write(b2);
|
|
||||||
outputStream.write(b3);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.write(b0);
|
|
||||||
listener.write(b1);
|
|
||||||
listener.write(b2);
|
|
||||||
listener.write(b3);
|
|
||||||
listener.notifyIfFull();
|
|
||||||
}
|
|
||||||
|
|
||||||
framesRecorded++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector2 cutoff(Vector2 vector) {
|
|
||||||
if (vector.getX() < -1) {
|
|
||||||
vector = vector.setX(-1);
|
|
||||||
} else if (vector.getX() > 1) {
|
|
||||||
vector = vector.setX(1);
|
|
||||||
}
|
|
||||||
if (vector.getY() < -1) {
|
|
||||||
vector = vector.setY(-1);
|
|
||||||
} else if (vector.getY() > 1) {
|
|
||||||
vector = vector.setY(1);
|
|
||||||
}
|
|
||||||
return vector;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector2 applyEffects(int frame, Vector2 vector) {
|
|
||||||
for (Effect effect : effects.values()) {
|
|
||||||
vector = effect.apply(frame, vector);
|
|
||||||
}
|
|
||||||
return vector;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setQuality(double quality) {
|
|
||||||
this.weight = quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Shape getCurrentShape() {
|
|
||||||
if (frame.size() == 0) {
|
|
||||||
return new Vector2();
|
|
||||||
}
|
|
||||||
|
|
||||||
return frame.get(currentShape);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
frame = frameQueue.take();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException("Initial frame not found. Cannot continue.");
|
|
||||||
}
|
|
||||||
|
|
||||||
audioEngine.play(this::generateChannels, renderLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
audioEngine.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addFrame(List<Shape> frame) {
|
|
||||||
try {
|
|
||||||
frameQueue.put(frame);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.err.println("Frame missed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addEffect(Object identifier, Effect effect) {
|
|
||||||
effects.put(identifier, effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeEffect(Object identifier) {
|
|
||||||
effects.remove(identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void read(byte[] buffer) throws InterruptedException {
|
|
||||||
Listener listener = new Listener(buffer);
|
|
||||||
try {
|
|
||||||
renderLock.lock();
|
|
||||||
listeners.add(listener);
|
|
||||||
} finally {
|
|
||||||
renderLock.unlock();
|
|
||||||
}
|
|
||||||
listener.waitUntilFull();
|
|
||||||
try {
|
|
||||||
renderLock.lock();
|
|
||||||
listeners.remove(listener);
|
|
||||||
} finally {
|
|
||||||
renderLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startRecord() {
|
|
||||||
outputStream = new ByteArrayOutputStream();
|
|
||||||
framesRecorded = 0;
|
|
||||||
recording = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int samplesPerSecond() {
|
|
||||||
return audioEngine.sampleRate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AudioInputStream stopRecord() {
|
|
||||||
recording = false;
|
|
||||||
byte[] input = outputStream.toByteArray();
|
|
||||||
outputStream = null;
|
|
||||||
|
|
||||||
AudioFormat audioFormat = new AudioFormat(audioEngine.sampleRate(), BITS_PER_SAMPLE, NUM_OUTPUTS, SIGNED, BIG_ENDIAN);
|
|
||||||
|
|
||||||
return new AudioInputStream(new ByteArrayInputStream(input), audioFormat, framesRecorded);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Listener {
|
|
||||||
private final byte[] buffer;
|
|
||||||
private final Semaphore sema;
|
|
||||||
|
|
||||||
private int offset;
|
|
||||||
|
|
||||||
private Listener(byte[] buffer) {
|
|
||||||
this.buffer = buffer;
|
|
||||||
this.sema = new Semaphore(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void waitUntilFull() throws InterruptedException {
|
|
||||||
sema.acquire();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyIfFull() {
|
|
||||||
if (offset >= buffer.length) {
|
|
||||||
sema.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void write(byte b) {
|
|
||||||
if (offset < buffer.length) {
|
|
||||||
buffer[offset++] = b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ package sh.ball.audio;
|
||||||
|
|
||||||
public class FrameProducer<S, T> implements Runnable {
|
public class FrameProducer<S, T> implements Runnable {
|
||||||
|
|
||||||
private final Renderer<S, T> renderer;
|
private final AudioPlayer<S, T> audioPlayer;
|
||||||
private final FrameSet<S> frames;
|
private final FrameSet<S> frames;
|
||||||
|
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
public FrameProducer(Renderer<S, T> renderer, FrameSet<S> frames) {
|
public FrameProducer(AudioPlayer<S, T> audioPlayer, FrameSet<S> frames) {
|
||||||
this.renderer = renderer;
|
this.audioPlayer = audioPlayer;
|
||||||
this.frames = frames;
|
this.frames = frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ public class FrameProducer<S, T> implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
running = true;
|
running = true;
|
||||||
while (running) {
|
while (running) {
|
||||||
renderer.addFrame(frames.next());
|
audioPlayer.addFrame(frames.next());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,14 @@ public class FrequencyAnalyser<S, T> implements Runnable {
|
||||||
// increase this for higher frequency resolution, but less frequent frequency calculation
|
// increase this for higher frequency resolution, but less frequent frequency calculation
|
||||||
private static final int DEFAULT_POWER_OF_TWO = 18;
|
private static final int DEFAULT_POWER_OF_TWO = 18;
|
||||||
|
|
||||||
private final Renderer<S, T> renderer;
|
private final AudioPlayer<S, T> audioPlayer;
|
||||||
private final List<FrequencyListener> listeners = new ArrayList<>();
|
private final List<FrequencyListener> listeners = new ArrayList<>();
|
||||||
private final int frameSize;
|
private final int frameSize;
|
||||||
private final int sampleRate;
|
private final int sampleRate;
|
||||||
private final int powerOfTwo;
|
private final int powerOfTwo;
|
||||||
|
|
||||||
public FrequencyAnalyser(Renderer<S, T> renderer, int frameSize, int sampleRate) {
|
public FrequencyAnalyser(AudioPlayer<S, T> audioPlayer, int frameSize, int sampleRate) {
|
||||||
this.renderer = renderer;
|
this.audioPlayer = audioPlayer;
|
||||||
this.frameSize = frameSize;
|
this.frameSize = frameSize;
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
this.powerOfTwo = (int) (DEFAULT_POWER_OF_TWO - Math.log(DEFAULT_SAMPLE_RATE / sampleRate) / Math.log(2));
|
this.powerOfTwo = (int) (DEFAULT_POWER_OF_TWO - Math.log(DEFAULT_SAMPLE_RATE / sampleRate) / Math.log(2));
|
||||||
|
@ -42,7 +42,7 @@ public class FrequencyAnalyser<S, T> implements Runnable {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
renderer.read(buf);
|
audioPlayer.read(buf);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
package sh.ball.audio;
|
|
||||||
|
|
||||||
import sh.ball.audio.effect.Effect;
|
|
||||||
|
|
||||||
public interface Renderer<S, T> extends Runnable {
|
|
||||||
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
void setQuality(double quality);
|
|
||||||
|
|
||||||
void addFrame(S frame);
|
|
||||||
|
|
||||||
void addEffect(Object identifier, Effect effect);
|
|
||||||
|
|
||||||
void removeEffect(Object identifier);
|
|
||||||
|
|
||||||
void read(byte[] buffer) throws InterruptedException;
|
|
||||||
|
|
||||||
void startRecord();
|
|
||||||
|
|
||||||
int samplesPerSecond();
|
|
||||||
|
|
||||||
T stopRecord();
|
|
||||||
}
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
package sh.ball.audio;
|
||||||
|
|
||||||
|
import sh.ball.audio.effect.Effect;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
|
import sh.ball.audio.engine.AudioEngine;
|
||||||
|
import sh.ball.shapes.Shape;
|
||||||
|
import sh.ball.shapes.Vector2;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
public class ShapeAudioPlayer implements AudioPlayer<List<Shape>, AudioInputStream> {
|
||||||
|
|
||||||
|
// Arbitrary max count for effects
|
||||||
|
private static final int MAX_COUNT = 10000;
|
||||||
|
private static final int BUFFER_SIZE = 5;
|
||||||
|
// Is this always true? Might need to check from AudioEngine
|
||||||
|
private static final int BITS_PER_SAMPLE = 16;
|
||||||
|
private static final boolean SIGNED = true;
|
||||||
|
private static final boolean BIG_ENDIAN = false;
|
||||||
|
// Stereo audio
|
||||||
|
private static final int NUM_OUTPUTS = 2;
|
||||||
|
|
||||||
|
private final AudioEngine audioEngine;
|
||||||
|
private final BlockingQueue<List<Shape>> frameQueue = new ArrayBlockingQueue<>(BUFFER_SIZE);
|
||||||
|
private final Map<Object, Effect> effects = new HashMap<>();
|
||||||
|
private final ReentrantLock renderLock = new ReentrantLock();
|
||||||
|
private final List<Listener> listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private ByteArrayOutputStream outputStream;
|
||||||
|
private boolean recording = false;
|
||||||
|
private int framesRecorded = 0;
|
||||||
|
private List<Shape> frame;
|
||||||
|
private int currentShape = 0;
|
||||||
|
private int audioFramesDrawn = 0;
|
||||||
|
private int count = 0;
|
||||||
|
|
||||||
|
private double weight = Shape.DEFAULT_WEIGHT;
|
||||||
|
|
||||||
|
public ShapeAudioPlayer(AudioEngine audioEngine) {
|
||||||
|
this.audioEngine = audioEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 generateChannels() throws InterruptedException {
|
||||||
|
Shape shape = getCurrentShape().setWeight(weight);
|
||||||
|
|
||||||
|
double totalAudioFrames = shape.getWeight() * shape.getLength();
|
||||||
|
double drawingProgress = totalAudioFrames == 0 ? 1 : audioFramesDrawn / totalAudioFrames;
|
||||||
|
Vector2 nextVector = applyEffects(count, shape.nextVector(drawingProgress));
|
||||||
|
|
||||||
|
Vector2 channels = cutoff(nextVector);
|
||||||
|
writeChannels((float) channels.getX(), (float) channels.getY());
|
||||||
|
|
||||||
|
audioFramesDrawn++;
|
||||||
|
|
||||||
|
if (++count > MAX_COUNT) {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioFramesDrawn > totalAudioFrames) {
|
||||||
|
audioFramesDrawn = 0;
|
||||||
|
currentShape++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentShape >= frame.size()) {
|
||||||
|
currentShape = 0;
|
||||||
|
frame = frameQueue.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeChannels(float leftChannel, float rightChannel) {
|
||||||
|
int left = (int)(leftChannel * Short.MAX_VALUE);
|
||||||
|
int right = (int)(rightChannel * Short.MAX_VALUE);
|
||||||
|
|
||||||
|
byte b0 = (byte) left;
|
||||||
|
byte b1 = (byte)(left >> 8);
|
||||||
|
byte b2 = (byte) right;
|
||||||
|
byte b3 = (byte)(right >> 8);
|
||||||
|
|
||||||
|
if (recording) {
|
||||||
|
outputStream.write(b0);
|
||||||
|
outputStream.write(b1);
|
||||||
|
outputStream.write(b2);
|
||||||
|
outputStream.write(b3);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.write(b0);
|
||||||
|
listener.write(b1);
|
||||||
|
listener.write(b2);
|
||||||
|
listener.write(b3);
|
||||||
|
listener.notifyIfFull();
|
||||||
|
}
|
||||||
|
|
||||||
|
framesRecorded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 cutoff(Vector2 vector) {
|
||||||
|
if (vector.getX() < -1) {
|
||||||
|
vector = vector.setX(-1);
|
||||||
|
} else if (vector.getX() > 1) {
|
||||||
|
vector = vector.setX(1);
|
||||||
|
}
|
||||||
|
if (vector.getY() < -1) {
|
||||||
|
vector = vector.setY(-1);
|
||||||
|
} else if (vector.getY() > 1) {
|
||||||
|
vector = vector.setY(1);
|
||||||
|
}
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 applyEffects(int frame, Vector2 vector) {
|
||||||
|
for (Effect effect : effects.values()) {
|
||||||
|
vector = effect.apply(frame, vector);
|
||||||
|
}
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setQuality(double quality) {
|
||||||
|
this.weight = quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Shape getCurrentShape() {
|
||||||
|
if (frame.size() == 0) {
|
||||||
|
return new Vector2();
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame.get(currentShape);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
frame = frameQueue.take();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException("Initial frame not found. Cannot continue.");
|
||||||
|
}
|
||||||
|
|
||||||
|
audioEngine.play(this::generateChannels, renderLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
audioEngine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addFrame(List<Shape> frame) {
|
||||||
|
try {
|
||||||
|
frameQueue.put(frame);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("Frame missed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addEffect(Object identifier, Effect effect) {
|
||||||
|
effects.put(identifier, effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeEffect(Object identifier) {
|
||||||
|
effects.remove(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(byte[] buffer) throws InterruptedException {
|
||||||
|
Listener listener = new Listener(buffer);
|
||||||
|
try {
|
||||||
|
renderLock.lock();
|
||||||
|
listeners.add(listener);
|
||||||
|
} finally {
|
||||||
|
renderLock.unlock();
|
||||||
|
}
|
||||||
|
listener.waitUntilFull();
|
||||||
|
try {
|
||||||
|
renderLock.lock();
|
||||||
|
listeners.remove(listener);
|
||||||
|
} finally {
|
||||||
|
renderLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startRecord() {
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
framesRecorded = 0;
|
||||||
|
recording = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int samplesPerSecond() {
|
||||||
|
return audioEngine.sampleRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AudioInputStream stopRecord() {
|
||||||
|
recording = false;
|
||||||
|
byte[] input = outputStream.toByteArray();
|
||||||
|
outputStream = null;
|
||||||
|
|
||||||
|
AudioFormat audioFormat = new AudioFormat(audioEngine.sampleRate(), BITS_PER_SAMPLE, NUM_OUTPUTS, SIGNED, BIG_ENDIAN);
|
||||||
|
|
||||||
|
return new AudioInputStream(new ByteArrayInputStream(input), audioFormat, framesRecorded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Listener {
|
||||||
|
private final byte[] buffer;
|
||||||
|
private final Semaphore sema;
|
||||||
|
|
||||||
|
private int offset;
|
||||||
|
|
||||||
|
private Listener(byte[] buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.sema = new Semaphore(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitUntilFull() throws InterruptedException {
|
||||||
|
sema.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyIfFull() {
|
||||||
|
if (offset >= buffer.length) {
|
||||||
|
sema.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(byte b) {
|
||||||
|
if (offset < buffer.length) {
|
||||||
|
buffer[offset++] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj");
|
private static final InputStream DEFAULT_OBJ = Controller.class.getResourceAsStream("/models/cube.obj");
|
||||||
|
|
||||||
private final FileChooser fileChooser = new FileChooser();
|
private final FileChooser fileChooser = new FileChooser();
|
||||||
private final Renderer<List<Shape>, AudioInputStream> renderer;
|
private final AudioPlayer<List<Shape>, AudioInputStream> audioPlayer;
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
private final int sampleRate;
|
private final int sampleRate;
|
||||||
|
@ -121,12 +121,12 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
@FXML
|
@FXML
|
||||||
private Slider wobbleSlider;
|
private Slider wobbleSlider;
|
||||||
|
|
||||||
public Controller(Renderer<List<Shape>, AudioInputStream> renderer) throws IOException {
|
public Controller(AudioPlayer<List<Shape>, AudioInputStream> audioPlayer) throws IOException {
|
||||||
this.renderer = renderer;
|
this.audioPlayer = audioPlayer;
|
||||||
FrameSet<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse();
|
FrameSet<List<Shape>> frames = new ObjParser(DEFAULT_OBJ).parse();
|
||||||
frames.addListener(this);
|
frames.addListener(this);
|
||||||
this.producer = new FrameProducer<>(renderer, frames);
|
this.producer = new FrameProducer<>(audioPlayer, frames);
|
||||||
this.sampleRate = renderer.samplesPerSecond();
|
this.sampleRate = audioPlayer.samplesPerSecond();
|
||||||
this.rotateEffect = new RotateEffect(sampleRate);
|
this.rotateEffect = new RotateEffect(sampleRate);
|
||||||
this.translateEffect = new TranslateEffect(sampleRate);
|
this.translateEffect = new TranslateEffect(sampleRate);
|
||||||
this.wobbleEffect = new WobbleEffect(sampleRate);
|
this.wobbleEffect = new WobbleEffect(sampleRate);
|
||||||
|
@ -136,7 +136,7 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
private Map<Slider, Consumer<Double>> initializeSliderMap() {
|
private Map<Slider, Consumer<Double>> initializeSliderMap() {
|
||||||
return Map.of(
|
return Map.of(
|
||||||
weightSlider,
|
weightSlider,
|
||||||
renderer::setQuality,
|
audioPlayer::setQuality,
|
||||||
rotateSpeedSlider,
|
rotateSpeedSlider,
|
||||||
rotateEffect::setSpeed,
|
rotateEffect::setSpeed,
|
||||||
translationSpeedSlider,
|
translationSpeedSlider,
|
||||||
|
@ -238,15 +238,15 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
|
|
||||||
updateObjectRotateSpeed();
|
updateObjectRotateSpeed();
|
||||||
|
|
||||||
renderer.addEffect(EffectType.SCALE, scaleEffect);
|
audioPlayer.addEffect(EffectType.SCALE, scaleEffect);
|
||||||
renderer.addEffect(EffectType.ROTATE, rotateEffect);
|
audioPlayer.addEffect(EffectType.ROTATE, rotateEffect);
|
||||||
renderer.addEffect(EffectType.TRANSLATE, translateEffect);
|
audioPlayer.addEffect(EffectType.TRANSLATE, translateEffect);
|
||||||
|
|
||||||
executor.submit(producer);
|
executor.submit(producer);
|
||||||
Thread renderThread = new Thread(renderer);
|
Thread renderThread = new Thread(audioPlayer);
|
||||||
renderThread.setUncaughtExceptionHandler((thread, throwable) -> throwable.printStackTrace());
|
renderThread.setUncaughtExceptionHandler((thread, throwable) -> throwable.printStackTrace());
|
||||||
renderThread.start();
|
renderThread.start();
|
||||||
FrequencyAnalyser<List<Shape>, AudioInputStream> analyser = new FrequencyAnalyser<>(renderer, 2, sampleRate);
|
FrequencyAnalyser<List<Shape>, AudioInputStream> analyser = new FrequencyAnalyser<>(audioPlayer, 2, sampleRate);
|
||||||
analyser.addListener(this);
|
analyser.addListener(this);
|
||||||
analyser.addListener(wobbleEffect);
|
analyser.addListener(wobbleEffect);
|
||||||
new Thread(analyser).start();
|
new Thread(analyser).start();
|
||||||
|
@ -257,10 +257,10 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
if (recording) {
|
if (recording) {
|
||||||
recordLabel.setText("Recording...");
|
recordLabel.setText("Recording...");
|
||||||
recordButton.setText("Stop Recording");
|
recordButton.setText("Stop Recording");
|
||||||
renderer.startRecord();
|
audioPlayer.startRecord();
|
||||||
} else {
|
} else {
|
||||||
recordButton.setText("Record");
|
recordButton.setText("Record");
|
||||||
AudioInputStream input = renderer.stopRecord();
|
AudioInputStream input = audioPlayer.stopRecord();
|
||||||
try {
|
try {
|
||||||
File file = fileChooser.showSaveDialog(stage);
|
File file = fileChooser.showSaveDialog(stage);
|
||||||
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
|
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
|
||||||
|
@ -315,10 +315,10 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
|
|
||||||
private void updateEffect(EffectType type, boolean checked, Effect effect) {
|
private void updateEffect(EffectType type, boolean checked, Effect effect) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
renderer.addEffect(type, effect);
|
audioPlayer.addEffect(type, effect);
|
||||||
effectTypes.get(type).setDisable(false);
|
effectTypes.get(type).setDisable(false);
|
||||||
} else {
|
} else {
|
||||||
renderer.removeEffect(type);
|
audioPlayer.removeEffect(type);
|
||||||
effectTypes.get(type).setDisable(true);
|
effectTypes.get(type).setDisable(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,7 +329,7 @@ public class Controller implements Initializable, FrequencyListener, Listener {
|
||||||
String path = file.getAbsolutePath();
|
String path = file.getAbsolutePath();
|
||||||
FrameSet<List<Shape>> frames = ParserFactory.getParser(path).parse();
|
FrameSet<List<Shape>> frames = ParserFactory.getParser(path).parse();
|
||||||
frames.addListener(this);
|
frames.addListener(this);
|
||||||
producer = new FrameProducer<>(renderer, frames);
|
producer = new FrameProducer<>(audioPlayer, frames);
|
||||||
|
|
||||||
updateObjectRotateSpeed();
|
updateObjectRotateSpeed();
|
||||||
updateFocalLength();
|
updateFocalLength();
|
||||||
|
|
|
@ -10,7 +10,7 @@ import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import sh.ball.audio.AudioPlayer;
|
import sh.ball.audio.ShapeAudioPlayer;
|
||||||
import sh.ball.audio.engine.XtAudioEngine;
|
import sh.ball.audio.engine.XtAudioEngine;
|
||||||
import sh.ball.engine.Vector3;
|
import sh.ball.engine.Vector3;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ public class Gui extends Application {
|
||||||
System.setProperty("prism.lcdtext", "false");
|
System.setProperty("prism.lcdtext", "false");
|
||||||
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml"));
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/osci-render.fxml"));
|
||||||
Controller controller = new Controller(new AudioPlayer(new XtAudioEngine()));
|
Controller controller = new Controller(new ShapeAudioPlayer(new XtAudioEngine()));
|
||||||
loader.setController(controller);
|
loader.setController(controller);
|
||||||
Parent root = loader.load();
|
Parent root = loader.load();
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue